File: CSharpRemoveUnnecessaryDiscardDesignationDiagnosticAnalyzer.cs
Web Access
Project: ..\..\..\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.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,
                   fadingOption: 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)
        {
            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 != null &&
                    recursivePattern.PositionalPatternClause.Subpatterns.Count == 1)
                {
                    return;
                }
 
                Report(discard);
            }
 
            return;
 
            void Report(DiscardDesignationSyntax discard)
            {
                context.ReportDiagnostic(Diagnostic.Create(
                    this.Descriptor,
                    discard.GetLocation()));
            }
        }
    }
}