File: CSharpRemoveUnusedValuesCodeFixProvider.cs
Web Access
Project: ..\..\..\src\CodeStyle\CSharp\CodeFixes\Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes)
// 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.
 
#nullable disable
 
using System.Collections.Generic;
using System.Composition;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.RemoveUnusedParametersAndValues;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.RemoveUnusedParametersAndValues
{
    [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveUnusedValues), Shared]
    [ExtensionOrder(After = PredefinedCodeFixProviderNames.AddImport)]
    internal class CSharpRemoveUnusedValuesCodeFixProvider :
        AbstractRemoveUnusedValuesCodeFixProvider<ExpressionSyntax, StatementSyntax, BlockSyntax,
            ExpressionStatementSyntax, LocalDeclarationStatementSyntax, VariableDeclaratorSyntax,
            ForEachStatementSyntax, SwitchSectionSyntax, SwitchLabelSyntax, CatchClauseSyntax, CatchClauseSyntax>
    {
        [ImportingConstructor]
        [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
        public CSharpRemoveUnusedValuesCodeFixProvider()
        {
        }
 
        protected override ISyntaxFormatting GetSyntaxFormatting()
            => CSharpSyntaxFormatting.Instance;
 
        protected override BlockSyntax WrapWithBlockIfNecessary(IEnumerable<StatementSyntax> statements)
            => SyntaxFactory.Block(statements);
 
        protected override SyntaxToken GetForEachStatementIdentifier(ForEachStatementSyntax node)
            => node.Identifier;
 
        protected override LocalDeclarationStatementSyntax GetCandidateLocalDeclarationForRemoval(VariableDeclaratorSyntax declarator)
            => declarator.Parent?.Parent as LocalDeclarationStatementSyntax;
 
        protected override SyntaxNode TryUpdateNameForFlaggedNode(SyntaxNode node, SyntaxToken newName)
        {
            switch (node.Kind())
            {
                case SyntaxKind.IdentifierName:
                    var identifierName = (IdentifierNameSyntax)node;
                    return identifierName.WithIdentifier(newName.WithTriviaFrom(identifierName.Identifier));
 
                case SyntaxKind.VariableDeclarator:
                    var variableDeclarator = (VariableDeclaratorSyntax)node;
                    if (newName.ValueText == AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.DiscardVariableName &&
                        variableDeclarator.Initializer?.Value is ImplicitObjectCreationExpressionSyntax implicitObjectCreation &&
                        variableDeclarator.Parent is VariableDeclarationSyntax parent)
                    {
                        // If we are generating a discard on the left of an initialization with an implicit object creation on the right,
                        // then we need to replace the implicit object creation with an explicit one.
                        // For example: 'TypeName v = new();' must be changed to '_ = new TypeName();'
                        var objectCreationNode = SyntaxFactory.ObjectCreationExpression(
                            newKeyword: implicitObjectCreation.NewKeyword,
                            type: parent.Type,
                            argumentList: implicitObjectCreation.ArgumentList,
                            initializer: implicitObjectCreation.Initializer);
                        variableDeclarator = variableDeclarator.WithInitializer(variableDeclarator.Initializer.WithValue(objectCreationNode));
                    }
 
                    return variableDeclarator.WithIdentifier(newName.WithTriviaFrom(variableDeclarator.Identifier));
 
                case SyntaxKind.SingleVariableDesignation:
                    return newName.ValueText == AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.DiscardVariableName
                        ? SyntaxFactory.DiscardDesignation().WithTriviaFrom(node)
                        : SyntaxFactory.SingleVariableDesignation(newName).WithTriviaFrom(node);
 
                case SyntaxKind.CatchDeclaration:
                    var catchDeclaration = (CatchDeclarationSyntax)node;
                    return catchDeclaration.WithIdentifier(newName.WithTriviaFrom(catchDeclaration.Identifier));
 
                case SyntaxKind.VarPattern:
                    return node.IsParentKind(SyntaxKind.Subpattern)
                        ? SyntaxFactory.DiscardPattern().WithTriviaFrom(node)
                        : SyntaxFactory.DiscardDesignation();
 
                default:
                    Debug.Fail($"Unexpected node kind for local/parameter declaration or reference: '{node.Kind()}'");
                    return null;
            }
        }
 
        protected override SyntaxNode TryUpdateParentOfUpdatedNode(SyntaxNode parent, SyntaxNode newNameNode, SyntaxEditor editor, ISyntaxFacts syntaxFacts, SemanticModel semanticModel)
        {
            if (newNameNode.IsKind(SyntaxKind.DiscardDesignation)
                && parent is DeclarationPatternSyntax declarationPattern
                && parent.SyntaxTree.Options.LanguageVersion() >= LanguageVersion.CSharp9)
            {
                var trailingTrivia = declarationPattern.Type.GetTrailingTrivia()
                    .AddRange(newNameNode.GetLeadingTrivia())
                    .AddRange(newNameNode.GetTrailingTrivia());
 
                return SyntaxFactory.TypePattern(declarationPattern.Type).WithTrailingTrivia(trailingTrivia);
            }
            else if (parent is AssignmentExpressionSyntax assignment &&
                assignment.Right is ImplicitObjectCreationExpressionSyntax implicitObjectCreation &&
                newNameNode is IdentifierNameSyntax { Identifier.ValueText: AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.DiscardVariableName } &&
                semanticModel.GetTypeInfo(implicitObjectCreation).Type is { } type)
            {
                // If we are generating a discard on the left of an assignment with an implicit object creation on the right,
                // then we need to replace the implicit object creation with an explicit one.
                // For example: 'v = new();' must be changed to '_ = new TypeOfV();'
                var objectCreationNode = SyntaxFactory.ObjectCreationExpression(
                    newKeyword: implicitObjectCreation.NewKeyword,
                    type: type.GenerateTypeSyntax(allowVar: false),
                    argumentList: implicitObjectCreation.ArgumentList,
                    initializer: implicitObjectCreation.Initializer);
                return assignment.Update((ExpressionSyntax)newNameNode, assignment.OperatorToken, objectCreationNode);
            }
 
            return null;
        }
 
        protected override void InsertAtStartOfSwitchCaseBlockForDeclarationInCaseLabelOrClause(SwitchSectionSyntax switchCaseBlock, SyntaxEditor editor, LocalDeclarationStatementSyntax declarationStatement)
        {
            var firstStatement = switchCaseBlock.Statements.FirstOrDefault();
            if (firstStatement != null)
            {
                editor.InsertBefore(firstStatement, declarationStatement);
            }
            else
            {
                // Switch section without any statements is an error case.
                // Insert before containing switch statement.
                editor.InsertBefore(switchCaseBlock.Parent, declarationStatement);
            }
        }
 
        protected override SyntaxNode GetReplacementNodeForCompoundAssignment(
            SyntaxNode originalCompoundAssignment,
            SyntaxNode newAssignmentTarget,
            SyntaxEditor editor,
            ISyntaxFactsService syntaxFacts)
        {
            // 1. Compound assignment is changed to simple assignment.
            // For example, "x += MethodCall();", where assignment to 'x' is redundant
            // is replaced with "_ = MethodCall();" or "var unused = MethodCall();
            //
            // 2. Null coalesce assignment is changed to assignment with null coalesce
            // expression on the right.
            // For example, "x ??= MethodCall();", where assignment to 'x' is redundant
            // is replaced with "_ = x ?? MethodCall();" or "var unused = x ?? MethodCall();
            //
            // 3. However, if the node is not parented by an expression statement then we
            // don't generate an assignment, but just the expression.
            // For example, "return x += MethodCall();" is replaced with "return x + MethodCall();"
            // and "return x ??= MethodCall();" is replaced with "return x ?? MethodCall();"
 
            if (originalCompoundAssignment is not AssignmentExpressionSyntax assignmentExpression)
            {
                Debug.Fail($"Unexpected kind for originalCompoundAssignment: {originalCompoundAssignment.Kind()}");
                return originalCompoundAssignment;
            }
 
            var leftOfAssignment = assignmentExpression.Left;
            var rightOfAssignment = assignmentExpression.Right;
 
            if (originalCompoundAssignment.Parent.IsKind(SyntaxKind.ExpressionStatement))
            {
                if (!originalCompoundAssignment.IsKind(SyntaxKind.CoalesceAssignmentExpression))
                {
                    // Case 1. Simple compound assignment parented by an expression statement.
                    return editor.Generator.AssignmentStatement(newAssignmentTarget, rightOfAssignment);
                }
                else
                {
                    // Case 2. Null coalescing compound assignment parented by an expression statement.
                    // Remove leading trivia from 'leftOfAssignment' as it should have been moved to 'newAssignmentTarget'.
                    leftOfAssignment = leftOfAssignment.WithoutLeadingTrivia();
                    return editor.Generator.AssignmentStatement(newAssignmentTarget,
                        SyntaxFactory.BinaryExpression(SyntaxKind.CoalesceExpression, leftOfAssignment, rightOfAssignment));
                }
            }
            else
            {
                // Case 3. Compound assignment not parented by an expression statement.
                var mappedBinaryExpressionKind = originalCompoundAssignment.Kind().MapCompoundAssignmentKindToBinaryExpressionKind();
                if (mappedBinaryExpressionKind == SyntaxKind.None)
                {
                    return originalCompoundAssignment;
                }
 
                return SyntaxFactory.BinaryExpression(mappedBinaryExpressionKind, leftOfAssignment, rightOfAssignment);
            }
        }
 
        protected override SyntaxNode GetReplacementNodeForVarPattern(SyntaxNode originalVarPattern, SyntaxNode newNameNode)
        {
            if (originalVarPattern is not VarPatternSyntax pattern)
                throw ExceptionUtilities.Unreachable();
 
            // If the replacement node is DiscardDesignationSyntax
            // then we need to just change the incoming var's pattern designation
            if (newNameNode is DiscardDesignationSyntax discardDesignation)
            {
                return pattern.WithDesignation(discardDesignation.WithTriviaFrom(pattern.Designation));
            }
 
            // Otherwise just return new node as a replacement.
            // This would be the default behaviour if there was no special case described above
            return newNameNode;
        }
    }
}