File: src\Analyzers\CSharp\Analyzers\RemoveUnusedParametersAndValues\CSharpRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs
Web Access
Project: src\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.Diagnostics;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.CodeStyle;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.RemoveUnusedParametersAndValues;
 
namespace Microsoft.CodeAnalysis.CSharp.RemoveUnusedParametersAndValues;
 
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpRemoveUnusedParametersAndValuesDiagnosticAnalyzer : AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer
{
    public CSharpRemoveUnusedParametersAndValuesDiagnosticAnalyzer()
        : base(unusedValueExpressionStatementOption: CSharpCodeStyleOptions.UnusedValueExpressionStatement,
               unusedValueAssignmentOption: CSharpCodeStyleOptions.UnusedValueAssignment)
    {
    }
 
    protected override ISyntaxFacts SyntaxFacts => CSharpSyntaxFacts.Instance;
 
    protected override bool SupportsDiscard(SyntaxTree tree)
        => tree.Options.LanguageVersion() >= LanguageVersion.CSharp7;
 
    protected override bool MethodHasHandlesClause(IMethodSymbol method)
        => false;
 
    protected override bool IsIfConditionalDirective(SyntaxNode node)
        => node is IfDirectiveTriviaSyntax;
 
    protected override bool ReturnsThrow(SyntaxNode? node)
    {
        if (node is not BaseMethodDeclarationSyntax methodSyntax)
        {
            return false;
        }
 
        if (methodSyntax.ExpressionBody is not null)
        {
            return methodSyntax.ExpressionBody.Expression is ThrowExpressionSyntax;
        }
 
        return methodSyntax.Body is { Statements: [ThrowStatementSyntax] };
    }
 
    protected override CodeStyleOption2<UnusedValuePreference> GetUnusedValueExpressionStatementOption(AnalyzerOptionsProvider provider)
        => ((CSharpAnalyzerOptionsProvider)provider).UnusedValueExpressionStatement;
 
    protected override CodeStyleOption2<UnusedValuePreference> GetUnusedValueAssignmentOption(AnalyzerOptionsProvider provider)
        => ((CSharpAnalyzerOptionsProvider)provider).UnusedValueAssignment;
 
    protected override bool ShouldBailOutFromRemovableAssignmentAnalysis(IOperation unusedSymbolWriteOperation)
    {
        // We don't want to recommend removing the write operation if it is within a statement
        // that is not parented by an explicit block with curly braces.
        // For example, assignment to 'x' in 'if (...) x = 1;'
        // Replacing 'x = 1' with an empty statement ';' is not useful, and user is most likely
        // going to remove the entire if statement in this case. However, we don't
        // want to suggest removing the entire if statement as that might lead to change of semantics.
        // So, we conservatively bail out from removable assignment analysis for such cases.
 
        var statementAncestor = unusedSymbolWriteOperation.Syntax.FirstAncestorOrSelf<StatementSyntax>()?.Parent;
        return statementAncestor is not (BlockSyntax or SwitchSectionSyntax);
    }
 
    // C# does not have an explicit "call" statement syntax for invocations with explicit value discard.
    protected override bool IsCallStatement(IExpressionStatementOperation expressionStatement)
        => false;
 
    protected override bool IsExpressionOfExpressionBody(IExpressionStatementOperation expressionStatementOperation)
        => expressionStatementOperation.Parent is IBlockOperation blockOperation &&
           !blockOperation.Syntax.IsKind(SyntaxKind.Block);
 
    protected override Location GetDefinitionLocationToFade(IOperation unusedDefinition)
    {
        switch (unusedDefinition.Syntax)
        {
            case VariableDeclaratorSyntax variableDeclarator:
                return variableDeclarator.Identifier.GetLocation();
 
            case DeclarationPatternSyntax declarationPattern:
                return declarationPattern.Designation.GetLocation();
 
            case RecursivePatternSyntax recursivePattern:
                Debug.Assert(recursivePattern.Designation is not null, "If we got to this point variable designation cannot be null");
                return recursivePattern.Designation!.GetLocation();
 
            case ListPatternSyntax listPattern:
                Debug.Assert(listPattern.Designation is not null, "If we got to this point variable designation cannot be null");
                return listPattern.Designation!.GetLocation();
 
            default:
                // C# syntax node for foreach statement has no syntax node for the loop control variable declaration,
                // so the operation tree has an IVariableDeclaratorOperation with the syntax mapped to the type node syntax instead of variable declarator syntax.
                // Check if the unused definition syntax is the foreach statement's type node.
                if (unusedDefinition.Syntax.Parent is ForEachStatementSyntax forEachStatement &&
                    forEachStatement.Type == unusedDefinition.Syntax)
                {
                    return forEachStatement.Identifier.GetLocation();
                }
 
                return unusedDefinition.Syntax.GetLocation();
        }
    }
}