File: src\roslyn\src\Analyzers\CSharp\Analyzers\RemoveUnnecessaryDiscardDesignation\CSharpRemoveUnnecessaryDiscardDesignationDiagnosticAnalyzer.cs
Web Access
Project: src\roslyn\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle)
// 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.Linq;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryDiscardDesignation;

/// <summary>
/// Supports code like <c>o switch { int _ => ... }</c> to just <c>o switch { int => ... }</c> in C# 9 and above.
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpRemoveUnnecessaryDiscardDesignationDiagnosticAnalyzer
    : AbstractBuiltInUnnecessaryCodeStyleDiagnosticAnalyzer
{
    public CSharpRemoveUnnecessaryDiscardDesignationDiagnosticAnalyzer()
        : base(IDEDiagnosticIds.RemoveUnnecessaryDiscardDesignationDiagnosticId,
               EnforceOnBuildValues.RemoveUnnecessaryDiscardDesignation,
               option: null,
               new LocalizableResourceString(nameof(CSharpAnalyzersResources.Remove_unnessary_discard), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)),
               new LocalizableResourceString(nameof(CSharpAnalyzersResources.Discard_can_be_removed), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
    {
    }

    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;

    protected override void InitializeWorker(AnalysisContext context)
    {
        context.RegisterCompilationStartAction(context =>
        {
            if (context.Compilation.LanguageVersion() < LanguageVersion.CSharp9)
                return;

            context.RegisterSyntaxNodeAction(AnalyzeDiscardDesignation, SyntaxKind.DiscardDesignation);
        });
    }

    private void AnalyzeDiscardDesignation(SyntaxNodeAnalysisContext context)
    {
        if (ShouldSkipAnalysis(context, notification: null))
            return;

        var semanticModel = context.SemanticModel;
        var cancellationToken = context.CancellationToken;

        var discard = (DiscardDesignationSyntax)context.Node;

        if (discard.Parent is DeclarationPatternSyntax declarationPattern)
        {
            // Don't perform semantic checks if we are in a 'simple' pattern like `x is A _`.
            // Since single identifier in cases like `x is A` binds stronger to type name for back compat reasons, we can safely remove discard anyway.
            if (declarationPattern.Parent is not IsPatternExpressionSyntax)
            {
                var typeSyntax = declarationPattern.Type;

                while (typeSyntax is QualifiedNameSyntax qualifiedName)
                    typeSyntax = qualifiedName.Left;

                if (typeSyntax is IdentifierNameSyntax identifierName &&
                    identifierName.GetAncestor<TypeDeclarationSyntax>() is { } containingTypeSyntax)
                {
                    var typeSymbol = semanticModel.GetRequiredDeclaredSymbol(containingTypeSyntax, cancellationToken);

                    // If we find other symbols with the same name in the type we are currently in, removing discard can lead to a compiler error.
                    // For instance, we can have a property in the type we are currently in with the same name as an identifier in the discard designation.
                    // Since a single identifier binds stronger to property name, we cannot remove discard.
                    if (typeSymbol.GetMembers(identifierName.Identifier.ValueText).Any())
                        return;
                }
            }

            Report(discard);
        }
        else if (discard.Parent is RecursivePatternSyntax recursivePattern)
        {
            // can't remove from `(int i) _` as `(int i)` is not a legal pattern itself.
            if (recursivePattern.PositionalPatternClause is { Subpatterns.Count: 1 })
            {
                return;
            }

            Report(discard);
        }

        return;

        void Report(DiscardDesignationSyntax discard)
        {
            context.ReportDiagnostic(Diagnostic.Create(
                this.Descriptor,
                discard.GetLocation()));
        }
    }
}