File: Microsoft.NetCore.Analyzers\Runtime\DetectPreviewFeatureAnalyzer.cs
Web Access
Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers)
// 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 System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.Analyzers.Runtime
{
    using static MicrosoftNetCoreAnalyzersResources;

    /// <summary>
    /// CA2252: <inheritdoc cref="DetectPreviewFeaturesTitle"/>
    /// Detect the use of [RequiresPreviewFeatures] in assemblies that have not opted into preview features
    /// </summary>
    public abstract class DetectPreviewFeatureAnalyzer : DiagnosticAnalyzer
    {
        internal const string RuleId = "CA2252";
        internal const string DefaultURL = "https://aka.ms/dotnet-warnings/preview-features";
        private static readonly LocalizableString s_localizableTitle = CreateLocalizableResourceString(nameof(DetectPreviewFeaturesTitle));
        private static readonly LocalizableString s_localizableDescription = CreateLocalizableResourceString(nameof(DetectPreviewFeaturesDescription));
        private static readonly ImmutableArray<SymbolKind> s_symbols = ImmutableArray.Create(SymbolKind.NamedType, SymbolKind.Method, SymbolKind.Property, SymbolKind.Field, SymbolKind.Event);

        internal static readonly DiagnosticDescriptor GeneralPreviewFeatureAttributeRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                    s_localizableTitle,
                                                                                                                    CreateLocalizableResourceString(nameof(DetectPreviewFeaturesMessage)),
                                                                                                                    DiagnosticCategory.Usage,
                                                                                                                    RuleLevel.BuildError,
                                                                                                                    s_localizableDescription,
                                                                                                                    isPortedFxCopRule: false,
                                                                                                                    isDataflowRule: false);
        internal static readonly DiagnosticDescriptor GeneralPreviewFeatureAttributeRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                    s_localizableTitle,
                                                                                                                    CreateLocalizableResourceString(nameof(DetectPreviewFeaturesMessageWithCustomMessagePlaceholder)),
                                                                                                                    DiagnosticCategory.Usage,
                                                                                                                    RuleLevel.BuildError,
                                                                                                                    s_localizableDescription,
                                                                                                                    isPortedFxCopRule: false,
                                                                                                                    isDataflowRule: false);

        internal static readonly DiagnosticDescriptor ImplementsPreviewInterfaceRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                s_localizableTitle,
                                                                                                                CreateLocalizableResourceString(nameof(ImplementsPreviewInterfaceMessage)),
                                                                                                                DiagnosticCategory.Usage,
                                                                                                                RuleLevel.BuildError,
                                                                                                                s_localizableDescription,
                                                                                                                isPortedFxCopRule: false,
                                                                                                                isDataflowRule: false);
        internal static readonly DiagnosticDescriptor ImplementsPreviewInterfaceRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                s_localizableTitle,
                                                                                                                CreateLocalizableResourceString(nameof(ImplementsPreviewInterfaceMessageWithCustomMessagePlaceholder)),
                                                                                                                DiagnosticCategory.Usage,
                                                                                                                RuleLevel.BuildError,
                                                                                                                s_localizableDescription,
                                                                                                                isPortedFxCopRule: false,
                                                                                                                isDataflowRule: false);

        internal static readonly DiagnosticDescriptor ImplementsPreviewMethodRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                s_localizableTitle,
                                                                                                                CreateLocalizableResourceString(nameof(ImplementsPreviewMethodMessage)),
                                                                                                                DiagnosticCategory.Usage,
                                                                                                                RuleLevel.BuildError,
                                                                                                                s_localizableDescription,
                                                                                                                isPortedFxCopRule: false,
                                                                                                                isDataflowRule: false);
        internal static readonly DiagnosticDescriptor ImplementsPreviewMethodRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                s_localizableTitle,
                                                                                                                CreateLocalizableResourceString(nameof(ImplementsPreviewMethodMessageWithCustomMessagePlaceholder)),
                                                                                                                DiagnosticCategory.Usage,
                                                                                                                RuleLevel.BuildError,
                                                                                                                s_localizableDescription,
                                                                                                                isPortedFxCopRule: false,
                                                                                                                isDataflowRule: false);

        internal static readonly DiagnosticDescriptor OverridesPreviewMethodRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                s_localizableTitle,
                                                                                                                CreateLocalizableResourceString(nameof(OverridesPreviewMethodMessage)),
                                                                                                                DiagnosticCategory.Usage,
                                                                                                                RuleLevel.BuildError,
                                                                                                                s_localizableDescription,
                                                                                                                isPortedFxCopRule: false,
                                                                                                                isDataflowRule: false);
        internal static readonly DiagnosticDescriptor OverridesPreviewMethodRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                s_localizableTitle,
                                                                                                                CreateLocalizableResourceString(nameof(OverridesPreviewMethodMessageWithCustomMessagePlaceholder)),
                                                                                                                DiagnosticCategory.Usage,
                                                                                                                RuleLevel.BuildError,
                                                                                                                s_localizableDescription,
                                                                                                                isPortedFxCopRule: false,
                                                                                                                isDataflowRule: false);

        internal static readonly DiagnosticDescriptor DerivesFromPreviewClassRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                             s_localizableTitle,
                                                                                                             CreateLocalizableResourceString(nameof(DerivesFromPreviewClassMessage)),
                                                                                                             DiagnosticCategory.Usage,
                                                                                                             RuleLevel.BuildError,
                                                                                                             s_localizableDescription,
                                                                                                             isPortedFxCopRule: false,
                                                                                                             isDataflowRule: false);
        internal static readonly DiagnosticDescriptor DerivesFromPreviewClassRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                             s_localizableTitle,
                                                                                                             CreateLocalizableResourceString(nameof(DerivesFromPreviewClassMessageWithCustomMessagePlaceholder)),
                                                                                                             DiagnosticCategory.Usage,
                                                                                                             RuleLevel.BuildError,
                                                                                                             s_localizableDescription,
                                                                                                             isPortedFxCopRule: false,
                                                                                                             isDataflowRule: false);

        internal static readonly DiagnosticDescriptor UsesPreviewTypeParameterRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                              s_localizableTitle,
                                                                                                              CreateLocalizableResourceString(nameof(UsesPreviewTypeParameterMessage)),
                                                                                                              DiagnosticCategory.Usage,
                                                                                                              RuleLevel.BuildError,
                                                                                                              s_localizableDescription,
                                                                                                              isPortedFxCopRule: false,
                                                                                                              isDataflowRule: false);
        internal static readonly DiagnosticDescriptor UsesPreviewTypeParameterRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                              s_localizableTitle,
                                                                                                              CreateLocalizableResourceString(nameof(UsesPreviewTypeParameterMessageWithCustomMessagePlaceholder)),
                                                                                                              DiagnosticCategory.Usage,
                                                                                                              RuleLevel.BuildError,
                                                                                                              s_localizableDescription,
                                                                                                              isPortedFxCopRule: false,
                                                                                                              isDataflowRule: false);

        internal static readonly DiagnosticDescriptor MethodReturnsPreviewTypeRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                              s_localizableTitle,
                                                                                                              CreateLocalizableResourceString(nameof(MethodReturnsPreviewTypeMessage)),
                                                                                                              DiagnosticCategory.Usage,
                                                                                                              RuleLevel.BuildError,
                                                                                                              s_localizableDescription,
                                                                                                              isPortedFxCopRule: false,
                                                                                                              isDataflowRule: false);
        internal static readonly DiagnosticDescriptor MethodReturnsPreviewTypeRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                              s_localizableTitle,
                                                                                                              CreateLocalizableResourceString(nameof(MethodReturnsPreviewTypeMessageWithCustomMessagePlaceholder)),
                                                                                                              DiagnosticCategory.Usage,
                                                                                                              RuleLevel.BuildError,
                                                                                                              s_localizableDescription,
                                                                                                              isPortedFxCopRule: false,
                                                                                                              isDataflowRule: false);

        internal static readonly DiagnosticDescriptor MethodUsesPreviewTypeAsParameterRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                      s_localizableTitle,
                                                                                                                      CreateLocalizableResourceString(nameof(MethodUsesPreviewTypeAsParameterMessage)),
                                                                                                                      DiagnosticCategory.Usage,
                                                                                                                      RuleLevel.BuildError,
                                                                                                                      s_localizableDescription,
                                                                                                                      isPortedFxCopRule: false,
                                                                                                                      isDataflowRule: false);
        internal static readonly DiagnosticDescriptor MethodUsesPreviewTypeAsParameterRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                      s_localizableTitle,
                                                                                                                      CreateLocalizableResourceString(nameof(MethodUsesPreviewTypeAsParameterMessageWithCustomMessagePlaceholder)),
                                                                                                                      DiagnosticCategory.Usage,
                                                                                                                      RuleLevel.BuildError,
                                                                                                                      s_localizableDescription,
                                                                                                                      isPortedFxCopRule: false,
                                                                                                                      isDataflowRule: false);
        internal static readonly DiagnosticDescriptor FieldOrEventIsPreviewTypeRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                        s_localizableTitle,
                                                                                                        CreateLocalizableResourceString(nameof(FieldIsPreviewTypeMessage)),
                                                                                                        DiagnosticCategory.Usage,
                                                                                                        RuleLevel.BuildError,
                                                                                                        s_localizableDescription,
                                                                                                        isPortedFxCopRule: false,
                                                                                                        isDataflowRule: false);
        internal static readonly DiagnosticDescriptor FieldOrEventIsPreviewTypeRuleWithCustomMessage = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                        s_localizableTitle,
                                                                                                        CreateLocalizableResourceString(nameof(FieldIsPreviewTypeMessageWithCustomMessagePlaceholder)),
                                                                                                        DiagnosticCategory.Usage,
                                                                                                        RuleLevel.BuildError,
                                                                                                        s_localizableDescription,
                                                                                                        isPortedFxCopRule: false,
                                                                                                        isDataflowRule: false);

        internal static readonly DiagnosticDescriptor StaticAbstractIsPreviewFeatureRule = DiagnosticDescriptorHelper.Create(RuleId,
                                                                                                                    s_localizableTitle,
                                                                                                                    CreateLocalizableResourceString(nameof(StaticAndAbstractRequiresPreviewFeatures)),
                                                                                                                    DiagnosticCategory.Usage,
                                                                                                                    RuleLevel.BuildError,
                                                                                                                    s_localizableDescription,
                                                                                                                    isPortedFxCopRule: false,
                                                                                                                    isDataflowRule: false);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
            GeneralPreviewFeatureAttributeRule,
            ImplementsPreviewInterfaceRule,
            ImplementsPreviewMethodRule,
            OverridesPreviewMethodRule,
            DerivesFromPreviewClassRule,
            UsesPreviewTypeParameterRule,
            MethodReturnsPreviewTypeRule,
            MethodUsesPreviewTypeAsParameterRule,
            FieldOrEventIsPreviewTypeRule,
            GeneralPreviewFeatureAttributeRuleWithCustomMessage,
            ImplementsPreviewInterfaceRuleWithCustomMessage,
            ImplementsPreviewMethodRuleWithCustomMessage,
            OverridesPreviewMethodRuleWithCustomMessage,
            DerivesFromPreviewClassRuleWithCustomMessage,
            UsesPreviewTypeParameterRuleWithCustomMessage,
            MethodReturnsPreviewTypeRuleWithCustomMessage,
            MethodUsesPreviewTypeAsParameterRuleWithCustomMessage,
            FieldOrEventIsPreviewTypeRuleWithCustomMessage,
            StaticAbstractIsPreviewFeatureRule);

        protected abstract SyntaxNode? GetPreviewInterfaceNodeForTypeImplementingPreviewInterface(ISymbol typeSymbol, ISymbol previewInterfaceSymbol);

        protected abstract SyntaxNode? GetConstraintSyntaxNodeForTypeConstrainedByPreviewTypes(ISymbol typeOrMethodSymbol, ISymbol previewInterfaceConstraintSymbol);

        protected abstract SyntaxNode? GetPreviewReturnTypeSyntaxNodeForMethodOrProperty(ISymbol methodOrPropertySymbol, ISymbol previewReturnTypeSymbol);

        protected abstract SyntaxNode? GetPreviewParameterSyntaxNodeForMethod(IMethodSymbol methodSymbol, ISymbol parameterSymbol);

        protected abstract SyntaxNode? GetPreviewSyntaxNodeForFieldsOrEvents(ISymbol fieldOrEventSymbol, ISymbol previewSymbol);

        protected abstract SyntaxNode? GetPreviewImplementsClauseSyntaxNodeForMethodOrProperty(ISymbol methodOrPropertySymbol, ISymbol previewSymbol);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
            context.RegisterCompilationStartAction(context =>
            {
                if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeVersioningRequiresPreviewFeaturesAttribute, out var previewFeaturesAttribute))
                {
                    return;
                }

                if (context.Compilation.Assembly.HasAnyAttribute(previewFeaturesAttribute))
                {
                    // This assembly has enabled preview attributes.
                    return;
                }

                ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols = new();

                IFieldSymbol? virtualStaticsInInterfaces = null;
                if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeCompilerServicesRuntimeFeature, out INamedTypeSymbol? runtimeFeatureType))
                {
                    virtualStaticsInInterfaces = runtimeFeatureType
                        .GetMembers("VirtualStaticsInInterfaces")
                        .OfType<IFieldSymbol>()
                        .FirstOrDefault();

                    if (virtualStaticsInInterfaces != null)
                    {
                        SymbolIsAnnotatedAsPreview(virtualStaticsInInterfaces, requiresPreviewFeaturesSymbols, previewFeaturesAttribute);
                    }
                }

                // Handle symbol operations involving preview features
                context.RegisterOperationAction(context => BuildSymbolInformationFromOperations(context, requiresPreviewFeaturesSymbols, previewFeaturesAttribute),
                    OperationKind.Invocation,
                    OperationKind.ObjectCreation,
                    OperationKind.PropertyReference,
                    OperationKind.FieldReference,
                    OperationKind.DelegateCreation,
                    OperationKind.EventReference,
                    OperationKind.Unary,
                    OperationKind.Binary,
                    OperationKind.ArrayCreation,
                    OperationKind.CatchClause,
                    OperationKind.TypeOf,
                    OperationKind.EventAssignment,
                    OperationKind.Await
                    );

                // Handle preview symbol definitions
                context.RegisterSymbolAction(context => AnalyzeSymbol(context, requiresPreviewFeaturesSymbols, virtualStaticsInInterfaces, previewFeaturesAttribute), s_symbols);
            });
        }

        /// <summary>
        /// Returns null if the type arguments are not preview
        /// </summary>
        /// <param name="typeArguments"></param>
        /// <returns></returns>
        /// <remarks>
        /// If the typeArguments was something like List[List[List[PreviewType]]]], this function will return PreviewType"
        /// </remarks>
        private static ISymbol? GetPreviewSymbolForGenericTypesFromTypeArguments(ImmutableArray<ITypeSymbol> typeArguments,
            ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
            INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            for (int i = 0; i < typeArguments.Length; i++)
            {
                ITypeSymbol typeParameter = typeArguments[i];
                while (typeParameter is IArrayTypeSymbol array)
                {
                    typeParameter = array.ElementType;
                }

                if (typeParameter is INamedTypeSymbol innerNamedType && innerNamedType.Arity > 0)
                {
                    ISymbol? previewSymbol = GetPreviewSymbolForGenericTypesFromTypeArguments(innerNamedType.TypeArguments, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol);
                    if (previewSymbol != null)
                    {
                        return previewSymbol;
                    }
                }

                if (SymbolIsAnnotatedAsPreview(typeParameter, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
                {
                    return typeParameter;
                }
            }

            return null;
        }

        private void ProcessFieldSymbolAttributes(SymbolAnalysisContext context,
                                                  IFieldSymbol symbol,
                                                  ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                  INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            ISymbol symbolType = symbol.Type;
            while (symbolType is IArrayTypeSymbol arrayType)
            {
                symbolType = arrayType.ElementType;
            }

            ProcessFieldOrEventSymbolAttributes(context, symbol, symbolType, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol);
        }

        private void ProcessFieldOrEventSymbolAttributes(SymbolAnalysisContext context,
                                                         ISymbol symbol,
                                                         ISymbol symbolType,
                                                         ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                         INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            if (SymbolIsAnnotatedAsPreview(symbolType, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
            {
                SyntaxNode? node = GetPreviewSyntaxNodeForFieldsOrEvents(symbol, symbolType);
                if (node != null)
                {
                    ReportDiagnosticWithCustomMessageIfItExists(context, node, symbolType, requiresPreviewFeaturesSymbols, FieldOrEventIsPreviewTypeRule, FieldOrEventIsPreviewTypeRuleWithCustomMessage, symbol.Name, symbolType.Name);
                }
                else
                {
                    ReportDiagnosticWithCustomMessageIfItExists(context, symbolType, symbol, requiresPreviewFeaturesSymbols, FieldOrEventIsPreviewTypeRule, FieldOrEventIsPreviewTypeRuleWithCustomMessage, symbol.Name, symbolType.Name);
                }
            }

            if (SymbolContainsGenericTypesWithPreviewAttributes(symbolType,
                                                                requiresPreviewFeaturesSymbols,
                                                                previewFeatureAttributeSymbol,
                                                                out ISymbol? previewSymbol,
                                                                out SyntaxNode? syntaxNode,
                                                                methodOrFieldOrEventSymbolForGenericParameterSyntaxNode: symbol))
            {
                if (syntaxNode != null)
                {
                    ReportDiagnosticWithCustomMessageIfItExists(context, syntaxNode, previewSymbol, requiresPreviewFeaturesSymbols, FieldOrEventIsPreviewTypeRule, FieldOrEventIsPreviewTypeRuleWithCustomMessage, symbol.Name, previewSymbol.Name);
                }
                else
                {
                    ReportDiagnosticWithCustomMessageIfItExists(context, previewSymbol, symbol, requiresPreviewFeaturesSymbols, FieldOrEventIsPreviewTypeRule, FieldOrEventIsPreviewTypeRuleWithCustomMessage, symbol.Name, previewSymbol.Name);
                }
            }
        }

        private void ProcessEventSymbolAttributes(SymbolAnalysisContext context,
                                                  IEventSymbol symbol,
                                                  ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                  INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            ISymbol symbolType = symbol.Type;
            while (symbolType is IArrayTypeSymbol arrayType)
            {
                symbolType = arrayType.ElementType;
            }

            ProcessFieldOrEventSymbolAttributes(context, symbol, symbolType, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol);
        }

        private void ProcessTypeSymbolAttributes(SymbolAnalysisContext context,
                                                 ITypeSymbol symbol,
                                                 ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                 INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            // We're only concerned about types(class/struct/interface) directly implementing preview interfaces. Implemented interfaces(direct/base) will report their diagnostics independently
            ImmutableArray<INamedTypeSymbol> interfaces = symbol.Interfaces;
            foreach (INamedTypeSymbol anInterface in interfaces)
            {
                if (SymbolIsAnnotatedAsPreview(anInterface, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
                {
                    SyntaxNode? interfaceNode = GetPreviewInterfaceNodeForTypeImplementingPreviewInterface(symbol, anInterface);
                    if (interfaceNode != null)
                    {
                        ReportDiagnosticWithCustomMessageIfItExists(context, interfaceNode, anInterface, requiresPreviewFeaturesSymbols, ImplementsPreviewInterfaceRule, ImplementsPreviewInterfaceRuleWithCustomMessage, symbol.Name, anInterface.Name);
                    }
                    else
                    {
                        ReportDiagnosticWithCustomMessageIfItExists(context, anInterface, symbol, requiresPreviewFeaturesSymbols, ImplementsPreviewInterfaceRule, ImplementsPreviewInterfaceRuleWithCustomMessage, symbol.Name, anInterface.Name);
                    }
                }
            }

            if (SymbolContainsGenericTypesWithPreviewAttributes(symbol,
                                                                requiresPreviewFeaturesSymbols,
                                                                previewFeatureAttributeSymbol,
                                                                out ISymbol? previewSymbol,
                                                                out SyntaxNode? syntaxNode))
            {
                if (syntaxNode != null)
                {
                    ReportDiagnosticWithCustomMessageIfItExists(context, syntaxNode, previewSymbol, requiresPreviewFeaturesSymbols, UsesPreviewTypeParameterRule, UsesPreviewTypeParameterRuleWithCustomMessage, symbol.Name, previewSymbol.Name);
                }
                else
                {
                    ReportDiagnosticWithCustomMessageIfItExists(context, previewSymbol, symbol, requiresPreviewFeaturesSymbols, UsesPreviewTypeParameterRule, UsesPreviewTypeParameterRuleWithCustomMessage, symbol.Name, previewSymbol.Name);
                }
            }

            INamedTypeSymbol? baseType = symbol.BaseType;
            if (baseType != null)
            {
                if (SymbolIsAnnotatedAsPreview(baseType, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
                {
                    SyntaxNode? baseTypeNode = GetPreviewInterfaceNodeForTypeImplementingPreviewInterface(symbol, baseType);
                    if (baseTypeNode != null)
                    {
                        ReportDiagnosticWithCustomMessageIfItExists(context, baseTypeNode, baseType, requiresPreviewFeaturesSymbols, DerivesFromPreviewClassRule, DerivesFromPreviewClassRuleWithCustomMessage, symbol.Name, baseType.Name);
                    }
                    else
                    {
                        ReportDiagnosticWithCustomMessageIfItExists(context, baseType, symbol, requiresPreviewFeaturesSymbols, DerivesFromPreviewClassRule, DerivesFromPreviewClassRuleWithCustomMessage, symbol.Name, baseType.Name);
                    }
                }
            }
        }

        private SyntaxNode? GetPreviewSyntaxNodeFromSymbols(ISymbol symbol,
                                                            ISymbol previewType)
        {
            switch (symbol)
            {
                case IFieldSymbol:
                case IEventSymbol:
                    return GetPreviewSyntaxNodeForFieldsOrEvents(symbol, previewType);
                case IMethodSymbol methodSymbol:
                    return GetPreviewParameterSyntaxNodeForMethod(methodSymbol, previewType);
                default:
                    return null;
            }
        }

        private bool SymbolContainsGenericTypesWithPreviewAttributes(ISymbol symbol,
                                                                     ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                                     INamedTypeSymbol previewFeatureAttribute,
                                                                     [NotNullWhen(true)] out ISymbol? previewSymbol,
                                                                     out SyntaxNode? previewSyntaxNode,
                                                                     bool checkTypeParametersForPreviewFeatures = true,
                                                                     ISymbol? methodOrFieldOrEventSymbolForGenericParameterSyntaxNode = null)
        {
            if (symbol is INamedTypeSymbol typeSymbol && typeSymbol.Arity > 0)
            {
                ISymbol? previewTypeArgument = GetPreviewSymbolForGenericTypesFromTypeArguments(typeSymbol.TypeArguments, requiresPreviewFeaturesSymbols, previewFeatureAttribute);
                if (previewTypeArgument != null)
                {
                    if (methodOrFieldOrEventSymbolForGenericParameterSyntaxNode != null)
                    {
                        previewSyntaxNode = GetPreviewSyntaxNodeFromSymbols(methodOrFieldOrEventSymbolForGenericParameterSyntaxNode, previewTypeArgument);
                    }
                    else
                    {
                        previewSyntaxNode = null;
                    }

                    previewSymbol = previewTypeArgument;
                    return true;
                }

                if (checkTypeParametersForPreviewFeatures)
                {
                    ImmutableArray<ITypeParameterSymbol> typeParameters = typeSymbol.TypeParameters;
                    if (TypeParametersHavePreviewAttribute(typeSymbol, typeParameters, requiresPreviewFeaturesSymbols, previewFeatureAttribute, out previewSymbol, out previewSyntaxNode))
                    {
                        return true;
                    }
                }
            }

            if (symbol is IMethodSymbol methodSymbol && methodSymbol.Arity > 0)
            {
                ISymbol? previewTypeArgument = GetPreviewSymbolForGenericTypesFromTypeArguments(methodSymbol.TypeArguments, requiresPreviewFeaturesSymbols, previewFeatureAttribute);
                if (previewTypeArgument != null)
                {
                    previewSyntaxNode = null;
                    previewSymbol = previewTypeArgument;
                    return true;
                }

                if (checkTypeParametersForPreviewFeatures)
                {
                    ImmutableArray<ITypeParameterSymbol> typeParameters = methodSymbol.TypeParameters;
                    if (TypeParametersHavePreviewAttribute(methodSymbol, typeParameters, requiresPreviewFeaturesSymbols, previewFeatureAttribute, out previewSymbol, out previewSyntaxNode))
                    {
                        return true;
                    }
                }
            }

            previewSymbol = null;
            previewSyntaxNode = null;
            return false;
        }

        private static bool SymbolIsStaticAndAbstract(ISymbol symbol, ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols, INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            // Static Abstract is only legal on interfaces. Anything else is likely a compile error and we shouldn't tag such cases.
            return symbol.IsStatic && symbol.IsAbstract && symbol.ContainingType != null && symbol.ContainingType.TypeKind == TypeKind.Interface && !SymbolIsAnnotatedAsPreview(symbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol);
        }

        private void ProcessPropertyOrMethodAttributes(SymbolAnalysisContext context,
                                                       ISymbol propertyOrMethodSymbol,
                                                       ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                       IFieldSymbol? virtualStaticsInInterfaces,
                                                       INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            if (SymbolIsStaticAndAbstract(propertyOrMethodSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
            {
                if (virtualStaticsInInterfaces != null && SymbolIsAnnotatedAsPreview(virtualStaticsInInterfaces, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
                {
                    context.ReportDiagnostic(propertyOrMethodSymbol.CreateDiagnostic(StaticAbstractIsPreviewFeatureRule));
                }
            }

            if (propertyOrMethodSymbol.IsImplementationOfAnyImplicitInterfaceMember(out ISymbol baseInterfaceMember))
            {
                if (SymbolIsAnnotatedAsPreview(baseInterfaceMember, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
                {
                    string baseInterfaceMemberName = baseInterfaceMember.ContainingSymbol != null ? baseInterfaceMember.ContainingSymbol.Name + "." + baseInterfaceMember.Name : baseInterfaceMember.Name;
                    SyntaxNode? previewImplementsClause = null;

                    if (propertyOrMethodSymbol.Language is LanguageNames.VisualBasic)
                    {
                        previewImplementsClause = GetPreviewImplementsClauseSyntaxNodeForMethodOrProperty(propertyOrMethodSymbol, baseInterfaceMember);
                    }

                    if (previewImplementsClause != null)
                    {
                        ReportDiagnosticWithCustomMessageIfItExists(context, previewImplementsClause, baseInterfaceMember, requiresPreviewFeaturesSymbols,
                            ImplementsPreviewMethodRule, ImplementsPreviewMethodRuleWithCustomMessage, propertyOrMethodSymbol.Name, baseInterfaceMemberName);
                    }
                    else
                    {
                        ReportDiagnosticWithCustomMessageIfItExists(context, baseInterfaceMember, propertyOrMethodSymbol, requiresPreviewFeaturesSymbols,
                            ImplementsPreviewMethodRule, ImplementsPreviewMethodRuleWithCustomMessage, propertyOrMethodSymbol.Name, baseInterfaceMemberName);
                    }
                }
            }

            if (propertyOrMethodSymbol.IsOverride)
            {
                ISymbol overridden = propertyOrMethodSymbol.GetOverriddenMember();
                if (SymbolIsAnnotatedAsPreview(overridden, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
                {
                    string overriddenName = overridden.ContainingSymbol != null ? overridden.ContainingSymbol.Name + "." + overridden.Name : overridden.Name;
                    ReportDiagnosticWithCustomMessageIfItExists(context, overridden, propertyOrMethodSymbol, requiresPreviewFeaturesSymbols, OverridesPreviewMethodRule, OverridesPreviewMethodRuleWithCustomMessage, propertyOrMethodSymbol.Name, overriddenName);
                }
            }

            if (propertyOrMethodSymbol is IMethodSymbol method)
            {
                ITypeSymbol methodReturnType = method.ReturnType;
                while (methodReturnType is IArrayTypeSymbol array)
                {
                    methodReturnType = array.ElementType;
                }

                if (SymbolIsAnnotatedAsPreview(methodReturnType, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
                {
                    SyntaxNode? returnTypeNode = GetPreviewReturnTypeSyntaxNodeForMethodOrProperty(method.IsPropertyGetter() ? method.AssociatedSymbol! : method, methodReturnType);
                    if (returnTypeNode != null)
                    {
                        ReportDiagnosticWithCustomMessageIfItExists(context, returnTypeNode, methodReturnType, requiresPreviewFeaturesSymbols, MethodReturnsPreviewTypeRule, MethodReturnsPreviewTypeRuleWithCustomMessage, propertyOrMethodSymbol.Name, methodReturnType.Name);
                    }
                    else
                    {
                        ReportDiagnosticWithCustomMessageIfItExists(context, methodReturnType, method, requiresPreviewFeaturesSymbols, MethodReturnsPreviewTypeRule, MethodReturnsPreviewTypeRuleWithCustomMessage, propertyOrMethodSymbol.Name, methodReturnType.Name);
                    }
                }

                if (methodReturnType is INamedTypeSymbol typeSymbol && typeSymbol.Arity > 0)
                {
                    ISymbol? innerPreviewSymbol = GetPreviewSymbolForGenericTypesFromTypeArguments(typeSymbol.TypeArguments, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol);
                    if (innerPreviewSymbol != null)
                    {
                        SyntaxNode? returnTypeNode = GetPreviewReturnTypeSyntaxNodeForMethodOrProperty(method.IsPropertyGetter() ? method.AssociatedSymbol! : method, innerPreviewSymbol);
                        if (returnTypeNode != null)
                        {
                            ReportDiagnosticWithCustomMessageIfItExists(context, returnTypeNode, innerPreviewSymbol, requiresPreviewFeaturesSymbols, MethodReturnsPreviewTypeRule, MethodReturnsPreviewTypeRuleWithCustomMessage, propertyOrMethodSymbol.Name, innerPreviewSymbol.Name);
                        }
                        else
                        {
                            ReportDiagnosticWithCustomMessageIfItExists(context, innerPreviewSymbol, method, requiresPreviewFeaturesSymbols, MethodReturnsPreviewTypeRule, MethodReturnsPreviewTypeRuleWithCustomMessage, propertyOrMethodSymbol.Name, innerPreviewSymbol.Name);
                        }
                    }
                }

                ImmutableArray<IParameterSymbol> parameters = method.Parameters;
                foreach (IParameterSymbol parameter in parameters)
                {
                    var parameterType = parameter.Type;
                    while (parameterType is IArrayTypeSymbol array)
                    {
                        parameterType = array.ElementType;
                    }

                    if (SymbolIsAnnotatedAsPreview(parameterType, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
                    {
                        SyntaxNode? previewParameterNode = GetPreviewParameterSyntaxNodeForMethod(method, parameterType);
                        if (previewParameterNode != null)
                        {
                            ReportDiagnosticWithCustomMessageIfItExists(context, previewParameterNode, parameterType, requiresPreviewFeaturesSymbols, MethodUsesPreviewTypeAsParameterRule, MethodUsesPreviewTypeAsParameterRuleWithCustomMessage, propertyOrMethodSymbol.Name, parameterType.Name);
                        }
                        else
                        {
                            ReportDiagnosticWithCustomMessageIfItExists(context, parameterType, parameter, requiresPreviewFeaturesSymbols, MethodUsesPreviewTypeAsParameterRule, MethodUsesPreviewTypeAsParameterRuleWithCustomMessage, propertyOrMethodSymbol.Name, parameterType.Name);
                        }
                    }

                    if (SymbolContainsGenericTypesWithPreviewAttributes(parameterType,
                                                                        requiresPreviewFeaturesSymbols,
                                                                        previewFeatureAttributeSymbol,
                                                                        out ISymbol? referencedPreviewSymbol,
                                                                        out SyntaxNode? syntaxNode,
                                                                        methodOrFieldOrEventSymbolForGenericParameterSyntaxNode: method))
                    {
                        if (syntaxNode != null)
                        {
                            ReportDiagnosticWithCustomMessageIfItExists(context, syntaxNode, referencedPreviewSymbol, requiresPreviewFeaturesSymbols, UsesPreviewTypeParameterRule, UsesPreviewTypeParameterRuleWithCustomMessage, propertyOrMethodSymbol.Name, referencedPreviewSymbol.Name);
                        }
                        else
                        {
                            ReportDiagnosticWithCustomMessageIfItExists(context, referencedPreviewSymbol, parameter, requiresPreviewFeaturesSymbols, UsesPreviewTypeParameterRule, UsesPreviewTypeParameterRuleWithCustomMessage, propertyOrMethodSymbol.Name, referencedPreviewSymbol.Name);
                        }
                    }
                }
            }

            if (SymbolContainsGenericTypesWithPreviewAttributes(propertyOrMethodSymbol,
                                                                requiresPreviewFeaturesSymbols,
                                                                previewFeatureAttributeSymbol,
                                                                out ISymbol? previewSymbol,
                                                                out SyntaxNode? referencedPreviewTypeSyntaxNode))
            {
                if (referencedPreviewTypeSyntaxNode != null)
                {
                    ReportDiagnosticWithCustomMessageIfItExists(context, referencedPreviewTypeSyntaxNode, previewSymbol, requiresPreviewFeaturesSymbols, UsesPreviewTypeParameterRule, UsesPreviewTypeParameterRuleWithCustomMessage, propertyOrMethodSymbol.Name, previewSymbol.Name);
                }
                else
                {
                    ReportDiagnosticWithCustomMessageIfItExists(context, previewSymbol, propertyOrMethodSymbol, requiresPreviewFeaturesSymbols, UsesPreviewTypeParameterRule, UsesPreviewTypeParameterRuleWithCustomMessage, propertyOrMethodSymbol.Name, previewSymbol.Name);
                }
            }
        }

        private void AnalyzeSymbol(SymbolAnalysisContext context,
                                   ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                   IFieldSymbol? virtualStaticsInInterfaces,
                                   INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            ISymbol symbol = context.Symbol;

            if (SymbolIsAnnotatedAsPreview(symbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol) ||
                (symbol is IMethodSymbol method && method.AssociatedSymbol != null && SymbolIsAnnotatedAsPreview(method.AssociatedSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol)))
            {
                return;
            }

            if (symbol is ITypeSymbol typeSymbol)
            {
                ProcessTypeSymbolAttributes(context, typeSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol);
            }
            else if (symbol is IMethodSymbol or IPropertySymbol)
            {
                ProcessPropertyOrMethodAttributes(context, symbol, requiresPreviewFeaturesSymbols, virtualStaticsInInterfaces, previewFeatureAttributeSymbol);
            }
            else if (symbol is IFieldSymbol fieldSymbol)
            {
                ProcessFieldSymbolAttributes(context, fieldSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol);
            }
            else if (symbol is IEventSymbol eventSymbol)
            {
                ProcessEventSymbolAttributes(context, eventSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol);
            }
        }

        private void BuildSymbolInformationFromOperations(OperationAnalysisContext context,
                                                          ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                          INamedTypeSymbol previewFeatureAttributeSymbol)
        {
            if (OperationUsesPreviewFeatures(context, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol, out ISymbol? symbol))
            {
                IOperation operation = context.Operation;
                if (operation is ICatchClauseOperation catchClauseOperation &&
                    catchClauseOperation.ExceptionDeclarationOrExpression is { } exceptionOperation)
                {
                    operation = exceptionOperation;
                }

                ReportDiagnosticWithCustomMessageIfItExists(context, operation, symbol, requiresPreviewFeaturesSymbols, GeneralPreviewFeatureAttributeRule, GeneralPreviewFeatureAttributeRuleWithCustomMessage, symbol.Name);
            }
        }

        private bool SymbolIsAnnotatedOrUsesPreviewTypes(ISymbol symbol,
                                                         ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                         INamedTypeSymbol previewFeatureAttributeSymbol,
                                                         [NotNullWhen(true)] out ISymbol? referencedPreviewSymbol)
        {
            if (SymbolIsAnnotatedAsPreview(symbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
            {
                referencedPreviewSymbol = symbol;
                return true;
            }

            if (SymbolContainsGenericTypesWithPreviewAttributes(symbol,
                                                                requiresPreviewFeaturesSymbols,
                                                                previewFeatureAttributeSymbol,
                                                                out referencedPreviewSymbol,
                                                                out SyntaxNode? _,
                                                                checkTypeParametersForPreviewFeatures: false))
            {
                return true;
            }

            referencedPreviewSymbol = null;
            return false;
        }

        private bool OperationUsesPreviewFeatures(OperationAnalysisContext context,
                                                  ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                  INamedTypeSymbol previewFeatureAttributeSymbol,
                                                  [NotNullWhen(true)] out ISymbol? referencedPreviewSymbol)
        {
            IOperation operation = context.Operation;
            ISymbol containingSymbol = context.ContainingSymbol;
            if (SymbolIsAnnotatedAsPreview(containingSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol))
            {
                referencedPreviewSymbol = null;
                return false;
            }

            ISymbol? symbol = GetOperationSymbol(operation);
            if (symbol != null)
            {
                if (symbol is IPropertySymbol propertySymbol)
                {
                    // bool AProperty => true is different from bool AProperty { get => false }. Handle both here
                    if (SymbolIsAnnotatedOrUsesPreviewTypes(propertySymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol, out referencedPreviewSymbol))
                    {
                        return true;
                    }

                    ValueUsageInfo usageInfo = operation.GetValueUsageInfo(propertySymbol);
                    if (usageInfo.IsReadFrom())
                    {
                        symbol = propertySymbol.GetMethod;
                    }

                    if (usageInfo.IsWrittenTo() || usageInfo == ValueUsageInfo.ReadWrite)
                    {
                        symbol = propertySymbol.SetMethod;
                    }
                }

                if (operation is IEventAssignmentOperation eventAssignment && symbol is IEventSymbol eventSymbol)
                {
                    symbol = eventAssignment.Adds ? eventSymbol.AddMethod : eventSymbol.RemoveMethod;
                }

                if (symbol == null)
                {
                    referencedPreviewSymbol = null;
                    return false;
                }

                if (symbol is IMethodSymbol methodSymbol && methodSymbol.IsConstructor())
                {
                    if (SymbolIsAnnotatedOrUsesPreviewTypes(methodSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol, out referencedPreviewSymbol))
                    {
                        // Constructor symbols have the name .ctor. Return the containing type instead so we get meaningful names in the diagnostic message
                        referencedPreviewSymbol = referencedPreviewSymbol.ContainingSymbol;
                        return true;
                    }

                    if (SymbolIsAnnotatedOrUsesPreviewTypes(methodSymbol.ContainingSymbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol, out referencedPreviewSymbol))
                    {
                        return true;
                    }
                }

                if (SymbolIsAnnotatedOrUsesPreviewTypes(symbol, requiresPreviewFeaturesSymbols, previewFeatureAttributeSymbol, out referencedPreviewSymbol))
                {
                    return true;
                }
            }

            referencedPreviewSymbol = null;
            return false;
        }

        private ISymbol? GetOperationSymbol(IOperation operation)
            => operation switch
            {
                IInvocationOperation iOperation => iOperation.TargetMethod,
                IObjectCreationOperation cOperation => cOperation.Constructor,
                IPropertyReferenceOperation pOperation => pOperation.Property,
                IFieldReferenceOperation fOperation => fOperation.Field,
                IDelegateCreationOperation dOperation => dOperation.Type,
                IEventReferenceOperation eOperation => eOperation.Member,
                IUnaryOperation uOperation => uOperation.OperatorMethod,
                IBinaryOperation bOperation => bOperation.OperatorMethod,
                IArrayCreationOperation arrayCreationOperation => SymbolFromArrayCreationOperation(arrayCreationOperation),
                ICatchClauseOperation catchClauseOperation => catchClauseOperation.ExceptionType,
                ITypeOfOperation typeOfOperation => typeOfOperation.TypeOperand,
                IEventAssignmentOperation eventAssignment => GetOperationSymbol(eventAssignment.EventReference),
                IAwaitOperation awaitOperation => SymbolFromAwaitOperation(awaitOperation),
                _ => null,
            };

        private static ISymbol? SymbolFromArrayCreationOperation(IArrayCreationOperation operation)
        {
            ISymbol? ret = operation.Type;
            while (ret is IArrayTypeSymbol arrayTypeSymbol)
            {
                ret = arrayTypeSymbol.ElementType;
            }

            return ret;
        }

        protected abstract ISymbol? SymbolFromAwaitOperation(IAwaitOperation operation);

        private bool TypeParametersHavePreviewAttribute(ISymbol namedTypeSymbolOrMethodSymbol,
                                                        ImmutableArray<ITypeParameterSymbol> typeParameters,
                                                        ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                        INamedTypeSymbol previewFeatureAttribute,
                                                        [NotNullWhen(true)] out ISymbol? previewSymbol,
                                                        out SyntaxNode? previewSyntaxNode)
        {
            foreach (ITypeParameterSymbol typeParameter in typeParameters)
            {
                ImmutableArray<ITypeSymbol> constraintTypes = typeParameter.ConstraintTypes;
                previewSymbol = GetPreviewSymbolForGenericTypesFromTypeArguments(constraintTypes, requiresPreviewFeaturesSymbols, previewFeatureAttribute);
                if (previewSymbol != null)
                {
                    previewSyntaxNode = GetConstraintSyntaxNodeForTypeConstrainedByPreviewTypes(namedTypeSymbolOrMethodSymbol, previewSymbol);
                    return true;
                }
            }

            previewSymbol = null;
            previewSyntaxNode = null;
            return false;
        }

#pragma warning disable CA1054,CA1055 // url should be of type URI
        private static string? GetMessageAndURLFromAttributeConstructor(AttributeData attribute, out string? url)
#pragma warning restore CA1054,CA1055
        {
            string? message = null;
            url = null;
            ImmutableArray<TypedConstant> constructorArguments = attribute.ConstructorArguments;
            if (constructorArguments.Length != 0)
            {
                if (constructorArguments.First().Value is string messageValue)
                {
                    message = messageValue;
                }
            }

            ImmutableArray<KeyValuePair<string, TypedConstant>> namedArguments = attribute.NamedArguments;
            if (namedArguments.Length != 0)
            {
                foreach (KeyValuePair<string, TypedConstant> namedArgument in namedArguments)
                {
                    if (namedArgument.Key == "Message" &&
                        namedArgument.Value.Value is string messageValue)
                    {
                        message = messageValue;
                    }

                    if (namedArgument.Key == "Url" &&
                        namedArgument.Value.Value is string urlValue)
                    {
                        url = urlValue;
                    }
                }
            }

            return message;
        }

        private static void ReportDiagnosticWithCustomMessageIfItExists(OperationAnalysisContext context,
                                                                        IOperation operation,
                                                                        ISymbol symbol,
                                                                        ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                                        DiagnosticDescriptor diagnosticDescriptor,
                                                                        DiagnosticDescriptor diagnosticDescriptorWithPlaceholdersForCustomMessage,
                                                                        string diagnosticMessageArgument)
        {
            if (!requiresPreviewFeaturesSymbols.TryGetValue(symbol, out (bool isPreview, string? message, string? url) existing))
            {
                Debug.Fail($"Should never reach this line. This means the symbol {symbol.Name} was not processed in this analyzer");
            }
            else
            {
                string url = existing.url ?? DefaultURL;
                if (existing.message is string customMessage)
                {
                    context.ReportDiagnostic(operation.CreateDiagnostic(diagnosticDescriptorWithPlaceholdersForCustomMessage, diagnosticMessageArgument, url, customMessage));
                }
                else
                {
                    context.ReportDiagnostic(operation.CreateDiagnostic(diagnosticDescriptor, diagnosticMessageArgument, url));
                }
            }
        }

        private static void ReportDiagnosticWithCustomMessageIfItExists(SymbolAnalysisContext context,
                                                                        SyntaxNode node,
                                                                        ISymbol previewSymbol,
                                                                        ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                                        DiagnosticDescriptor diagnosticDescriptor,
                                                                        DiagnosticDescriptor diagnosticDescriptorWithPlaceholdersForCustomMessage,
                                                                        string diagnosticMessageArgument0,
                                                                        string diagnosticMessageArgument1)
        {
            if (!requiresPreviewFeaturesSymbols.TryGetValue(previewSymbol, out (bool isPreview, string? message, string? url) existing))
            {
                Debug.Fail($"Should never reach this line. This means the symbol {previewSymbol.Name} was not processed in this analyzer");
            }
            else
            {
                string url = existing.url ?? DefaultURL;
                if (existing.message is string customMessage)
                {
                    context.ReportDiagnostic(node.CreateDiagnostic(diagnosticDescriptorWithPlaceholdersForCustomMessage, diagnosticMessageArgument0, diagnosticMessageArgument1, url, customMessage));
                }
                else
                {
                    context.ReportDiagnostic(node.CreateDiagnostic(diagnosticDescriptor, diagnosticMessageArgument0, diagnosticMessageArgument1, url));
                }
            }
        }

        private static void ReportDiagnosticWithCustomMessageIfItExists(SymbolAnalysisContext context,
                                                                        ISymbol previewSymbol,
                                                                        ISymbol symbolToRaiseDiagnosticOn,
                                                                        ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols,
                                                                        DiagnosticDescriptor diagnosticDescriptor,
                                                                        DiagnosticDescriptor diagnosticDescriptorWithPlaceholdersForCustomMessage,
                                                                        string diagnosticMessageArgument0,
                                                                        string diagnosticMessageArgument1)
        {
            if (!requiresPreviewFeaturesSymbols.TryGetValue(previewSymbol, out (bool isPreview, string? message, string? url) existing))
            {
                Debug.Fail($"Should never reach this line. This means the symbol {previewSymbol.Name} was not processed in this analyzer");
            }
            else
            {
                string url = existing.url ?? DefaultURL;
                if (existing.message is string customMessage)
                {
                    context.ReportDiagnostic(symbolToRaiseDiagnosticOn.CreateDiagnostic(diagnosticDescriptorWithPlaceholdersForCustomMessage, diagnosticMessageArgument0, diagnosticMessageArgument1, url, customMessage));
                }
                else
                {
                    context.ReportDiagnostic(symbolToRaiseDiagnosticOn.CreateDiagnostic(diagnosticDescriptor, diagnosticMessageArgument0, diagnosticMessageArgument1, url));
                }
            }
        }

        private static bool SymbolIsAnnotatedAsPreview(ISymbol symbol, ConcurrentDictionary<ISymbol, (bool isPreview, string? message, string? url)> requiresPreviewFeaturesSymbols, INamedTypeSymbol previewFeatureAttribute)
        {
            if (symbol is null)
            {
                // We are sometimes null, such as for IPropertySymbol.GetOverriddenMember()
                // when the property symbol represents an indexer, so return false as a precaution
                return false;
            }

            if (!requiresPreviewFeaturesSymbols.TryGetValue(symbol, out (bool isPreview, string? message, string? url) existing))
            {
                if (symbol.GetAttribute(previewFeatureAttribute) is { } attribute)
                {
                    string? message = GetMessageAndURLFromAttributeConstructor(attribute, out string? url);
                    requiresPreviewFeaturesSymbols.GetOrAdd(symbol, new ValueTuple<bool, string?, string?>(true, message, url));
                    return true;
                }

                ISymbol? parent = symbol.ContainingSymbol;
                while (parent is INamespaceSymbol)
                {
                    parent = parent.ContainingSymbol;
                }

                if (parent != null)
                {
                    if (SymbolIsAnnotatedAsPreview(parent, requiresPreviewFeaturesSymbols, previewFeatureAttribute))
                    {
                        requiresPreviewFeaturesSymbols.GetOrAdd(symbol, new ValueTuple<bool, string?, string?>(true, null, null));
                        return true;
                    }
                }

                requiresPreviewFeaturesSymbols.GetOrAdd(symbol, new ValueTuple<bool, string?, string?>(false, null, null));
                return false;
            }

            return existing.isPreview;
        }
    }
}