File: Diagnostics\Analyzers\CSharpSimplifyTypeNamesDiagnosticAnalyzer.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// 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;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Simplification;
using Microsoft.CodeAnalysis.CSharp.Simplification.Simplifiers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.SimplifyTypeNames;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.CSharp.Diagnostics.SimplifyTypeNames;
 
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpSimplifyTypeNamesDiagnosticAnalyzer
    : SimplifyTypeNamesDiagnosticAnalyzerBase<SyntaxKind, CSharpSimplifierOptions>
{
    private static readonly ImmutableArray<SyntaxKind> s_kindsOfInterest =
        [
            SyntaxKind.QualifiedName,
            SyntaxKind.AliasQualifiedName,
            SyntaxKind.GenericName,
            SyntaxKind.IdentifierName,
            SyntaxKind.SimpleMemberAccessExpression,
            SyntaxKind.QualifiedCref,
        ];
 
    protected override bool IsIgnoredCodeBlock(SyntaxNode codeBlock)
    {
        // Avoid analysis of compilation units and types in AnalyzeCodeBlock. These nodes appear in code block
        // callbacks when they include attributes, but analysis of the node at this level would block more efficient
        // analysis of descendant members.
        return codeBlock.Kind() is
            SyntaxKind.CompilationUnit or
            SyntaxKind.ClassDeclaration or
            SyntaxKind.RecordDeclaration or
            SyntaxKind.StructDeclaration or
            SyntaxKind.RecordStructDeclaration or
            SyntaxKind.InterfaceDeclaration or
            SyntaxKind.DelegateDeclaration or
            SyntaxKind.EnumDeclaration;
    }
 
    protected override ImmutableArray<Diagnostic> AnalyzeCodeBlock(CodeBlockAnalysisContext context, SyntaxNode root)
    {
        Debug.Assert(context.CodeBlock.DescendantNodesAndSelf().Contains(root));
 
        var semanticModel = context.SemanticModel;
        var cancellationToken = context.CancellationToken;
 
        var options = context.GetCSharpAnalyzerOptions().GetSimplifierOptions();
        if (ShouldSkipAnalysis(context.FilterTree, context.Options, context.SemanticModel.Compilation.Options, GetAllNotifications(options), cancellationToken))
            return [];
 
        using var simplifier = new TypeSyntaxSimplifierWalker(this, semanticModel, options, context.Options, ignoredSpans: null, cancellationToken);
        simplifier.Visit(root);
        return simplifier.Diagnostics;
    }
 
    protected override ImmutableArray<Diagnostic> AnalyzeSemanticModel(SemanticModelAnalysisContext context, SyntaxNode root, TextSpanMutableIntervalTree? codeBlockIntervalTree)
    {
        var options = context.GetCSharpAnalyzerOptions().GetSimplifierOptions();
        if (ShouldSkipAnalysis(context.FilterTree, context.Options, context.SemanticModel.Compilation.Options, GetAllNotifications(options), context.CancellationToken))
            return [];
 
        var simplifier = new TypeSyntaxSimplifierWalker(this, context.SemanticModel, options, context.Options, ignoredSpans: codeBlockIntervalTree, context.CancellationToken);
        simplifier.Visit(root);
        return simplifier.Diagnostics;
    }
 
    internal override bool IsCandidate(SyntaxNode node)
        => node != null && s_kindsOfInterest.Contains(node.Kind());
 
    internal override bool CanSimplifyTypeNameExpression(
        SemanticModel model, SyntaxNode node, CSharpSimplifierOptions options,
        out TextSpan issueSpan, out string diagnosticId, out bool inDeclaration,
        CancellationToken cancellationToken)
    {
        inDeclaration = false;
        issueSpan = default;
        diagnosticId = IDEDiagnosticIds.SimplifyNamesDiagnosticId;
 
        if (node is MemberAccessExpressionSyntax memberAccess && memberAccess.Expression.IsKind(SyntaxKind.ThisExpression))
        {
            // don't bother analyzing "this.Goo" expressions.  They will be analyzed by
            // the CSharpSimplifyThisOrMeDiagnosticAnalyzer.
            return false;
        }
 
        if (node.ContainsDiagnostics)
        {
            return false;
        }
 
        SyntaxNode replacementSyntax;
        if (node is QualifiedCrefSyntax crefSyntax)
        {
            if (!QualifiedCrefSimplifier.Instance.TrySimplify(crefSyntax, model, options, out var replacement, out issueSpan, cancellationToken))
                return false;
 
            replacementSyntax = replacement;
        }
        else
        {
            if (!ExpressionSimplifier.Instance.TrySimplify((ExpressionSyntax)node, model, options, out var replacement, out issueSpan, cancellationToken))
                return false;
 
            replacementSyntax = replacement;
        }
 
        // set proper diagnostic ids.
        if (replacementSyntax.HasAnnotations(nameof(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInDeclaration)))
        {
            inDeclaration = true;
            diagnosticId = IDEDiagnosticIds.PreferBuiltInOrFrameworkTypeDiagnosticId;
        }
        else if (replacementSyntax.HasAnnotations(nameof(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess)))
        {
            inDeclaration = false;
            diagnosticId = IDEDiagnosticIds.PreferBuiltInOrFrameworkTypeDiagnosticId;
        }
        else if (node.Kind() == SyntaxKind.SimpleMemberAccessExpression)
        {
            diagnosticId = IDEDiagnosticIds.SimplifyMemberAccessDiagnosticId;
        }
 
        return true;
    }
}