File: CSharpRemoveUnusedParametersAndValuesDiagnosticAnalyzer.cs
Web Access
Project: ..\..\..\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 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 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;
            switch (statementAncestor)
            {
                case BlockSyntax _:
                case SwitchSectionSyntax _:
                    return false;
 
                default:
                    return true;
            }
        }
 
        // 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();
 
                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();
            }
        }
    }
}