File: MetaAnalyzers\DiagnosticAnalyzerFieldsAnalyzer.cs
Web Access
Project: src\src\RoslynAnalyzers\Microsoft.CodeAnalysis.Analyzers\Core\Microsoft.CodeAnalysis.Analyzers.csproj (Microsoft.CodeAnalysis.Analyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers.Helpers;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.CodeAnalysis.Analyzers.MetaAnalyzers
{
    using static CodeAnalysisDiagnosticsResources;
 
    public abstract class DiagnosticAnalyzerFieldsAnalyzer<
        TClassDeclarationSyntax,
        TStructDeclarationSyntax,
        TFieldDeclarationSyntax,
        TTypeSyntax,
        TVariableTypeDeclarationSyntax,
        TTypeArgumentListSyntax,
        TGenericNameSyntax
    > : DiagnosticAnalyzerCorrectnessAnalyzer
        where TClassDeclarationSyntax : SyntaxNode
        where TStructDeclarationSyntax : SyntaxNode
        where TFieldDeclarationSyntax : SyntaxNode
        where TTypeSyntax : SyntaxNode
        where TVariableTypeDeclarationSyntax : SyntaxNode
        where TTypeArgumentListSyntax : SyntaxNode
        where TGenericNameSyntax : SyntaxNode
    {
        private static readonly string s_compilationTypeFullName = typeof(Compilation).FullName;
        private static readonly string s_symbolTypeFullName = typeof(ISymbol).FullName;
        private static readonly string s_operationTypeFullName = typeof(IOperation).FullName;
 
        public static readonly DiagnosticDescriptor DoNotStorePerCompilationDataOntoFieldsRule = new(
            DiagnosticIds.DoNotStorePerCompilationDataOntoFieldsRuleId,
            CreateLocalizableResourceString(nameof(DoNotStorePerCompilationDataOntoFieldsTitle)),
            CreateLocalizableResourceString(nameof(DoNotStorePerCompilationDataOntoFieldsMessage)),
            DiagnosticCategory.MicrosoftCodeAnalysisPerformance,
            DiagnosticSeverity.Warning,
            isEnabledByDefault: true,
            description: CreateLocalizableResourceString(nameof(DoNotStorePerCompilationDataOntoFieldsDescription), nameof(AnalysisContext), DiagnosticWellKnownNames.RegisterCompilationStartActionName),
            customTags: WellKnownDiagnosticTagsExtensions.Telemetry);
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DoNotStorePerCompilationDataOntoFieldsRule);
 
#pragma warning disable RS1025 // Configure generated code analysis
        public override void Initialize(AnalysisContext context)
#pragma warning restore RS1025 // Configure generated code analysis
        {
            context.EnableConcurrentExecution();
 
            base.Initialize(context);
        }
 
        [SuppressMessage("AnalyzerPerformance", "RS1012:Start action has no registered actions.", Justification = "Method returns an analyzer that is registered by the caller.")]
        protected override DiagnosticAnalyzerSymbolAnalyzer? GetDiagnosticAnalyzerSymbolAnalyzer(CompilationStartAnalysisContext compilationContext, INamedTypeSymbol diagnosticAnalyzer, INamedTypeSymbol diagnosticAnalyzerAttribute)
        {
            WellKnownTypeProvider typeProvider = WellKnownTypeProvider.GetOrCreate(compilationContext.Compilation);
 
            INamedTypeSymbol? compilationType = typeProvider.GetOrCreateTypeByMetadataName(s_compilationTypeFullName);
            if (compilationType == null)
            {
                return null;
            }
 
            INamedTypeSymbol? symbolType = typeProvider.GetOrCreateTypeByMetadataName(s_symbolTypeFullName);
            if (symbolType == null)
            {
                return null;
            }
 
            INamedTypeSymbol? operationType = typeProvider.GetOrCreateTypeByMetadataName(s_operationTypeFullName);
            if (operationType == null)
            {
                return null;
            }
 
            var attributeUsageAttribute = typeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemAttributeUsageAttribute);
 
            return new FieldsAnalyzer(compilationType, symbolType, operationType, attributeUsageAttribute, diagnosticAnalyzer, diagnosticAnalyzerAttribute);
        }
 
        private sealed class FieldsAnalyzer : SyntaxNodeWithinAnalyzerTypeCompilationAnalyzer<TClassDeclarationSyntax, TStructDeclarationSyntax, TFieldDeclarationSyntax>
        {
            private readonly INamedTypeSymbol _compilationType;
            private readonly INamedTypeSymbol _symbolType;
            private readonly INamedTypeSymbol _operationType;
            private readonly INamedTypeSymbol? _attributeUsageAttribute;
 
            public FieldsAnalyzer(INamedTypeSymbol compilationType,
                INamedTypeSymbol symbolType,
                INamedTypeSymbol operationType,
                INamedTypeSymbol? attributeUsageAttribute,
                INamedTypeSymbol diagnosticAnalyzer,
                INamedTypeSymbol diagnosticAnalyzerAttribute)
                : base(diagnosticAnalyzer, diagnosticAnalyzerAttribute)
            {
                _compilationType = compilationType;
                _symbolType = symbolType;
                _operationType = operationType;
                _attributeUsageAttribute = attributeUsageAttribute;
            }
 
            protected override void AnalyzeDiagnosticAnalyzer(SymbolAnalysisContext symbolContext)
            {
                var namedType = (INamedTypeSymbol)symbolContext.Symbol;
                if (!HasDiagnosticAnalyzerAttribute(namedType, _attributeUsageAttribute))
                {
                    // We are interested only in DiagnosticAnalyzer types with DiagnosticAnalyzerAttribute.
                    return;
                }
 
                base.AnalyzeDiagnosticAnalyzer(symbolContext);
            }
 
            protected override void AnalyzeNode(SymbolAnalysisContext symbolContext, TFieldDeclarationSyntax syntaxNode, SemanticModel semanticModel)
            {
                // Get all the type syntax nodes within the topmost type declaration nodes for field declarations.
                System.Collections.Generic.IEnumerable<TVariableTypeDeclarationSyntax> variableTypeDeclarations = syntaxNode.DescendantNodesAndSelf().OfType<TVariableTypeDeclarationSyntax>();
                System.Collections.Generic.IEnumerable<TTypeSyntax> topMostTypeNodes = variableTypeDeclarations.SelectMany(typeDecl => typeDecl.ChildNodes().OfType<TTypeSyntax>());
                System.Collections.Generic.IEnumerable<TTypeSyntax> typeNodes = topMostTypeNodes.SelectMany(t => t.DescendantNodesAndSelf().OfType<TTypeSyntax>());
 
                foreach (TTypeSyntax typeNode in typeNodes)
                {
                    if (IsContainedInFuncOrAction(typeNode, semanticModel))
                    {
                        continue;
                    }
 
                    ITypeSymbol? type = semanticModel.GetTypeInfo(typeNode, symbolContext.CancellationToken).Type;
                    if (type != null)
                    {
                        foreach (ITypeSymbol innerType in type.GetBaseTypesAndThis())
                        {
                            if (SymbolEqualityComparer.Default.Equals(innerType, _compilationType))
                            {
                                ReportDiagnostic(type, typeNode, symbolContext);
                                return;
                            }
                        }
 
                        if (SymbolEqualityComparer.Default.Equals(type, _symbolType) || SymbolEqualityComparer.Default.Equals(type, _operationType))
                        {
                            ReportDiagnostic(type, typeNode, symbolContext);
                            return;
                        }
 
                        foreach (INamedTypeSymbol iface in type.AllInterfaces)
                        {
                            if (SymbolEqualityComparer.Default.Equals(iface, _symbolType) || SymbolEqualityComparer.Default.Equals(iface, _operationType))
                            {
                                ReportDiagnostic(type, typeNode, symbolContext);
                                return;
                            }
                        }
                    }
                }
            }
 
            private static void ReportDiagnostic(ITypeSymbol type, TTypeSyntax typeSyntax, SymbolAnalysisContext context)
            {
                Diagnostic diagnostic = typeSyntax.CreateDiagnostic(DoNotStorePerCompilationDataOntoFieldsRule, type.ToDisplayString());
                context.ReportDiagnostic(diagnostic);
            }
        }
 
        private static bool IsContainedInFuncOrAction(TTypeSyntax typeSyntax, SemanticModel model)
        {
            var current = typeSyntax.Parent;
            while (current is TTypeArgumentListSyntax or TGenericNameSyntax)
            {
                if (current is TGenericNameSyntax && model.GetSymbolInfo(current).Symbol is INamedTypeSymbol { DelegateInvokeMethod: not null })
                {
                    return true;
                }
 
                current = current.Parent;
            }
 
            return false;
        }
    }
}