Esempio n. 1
0
        private async Task FixAllAsync(
            string diagnosticId,
            IOrderedEnumerable <Diagnostic> diagnostics,
            Document document,
            SemanticModel semanticModel,
            SyntaxNode root,
            SyntaxNode containingMemberDeclaration,
            UnusedValuePreference preference,
            bool removeAssignments,
            UniqueVariableNameGenerator nameGenerator,
            SyntaxEditor editor,
            ISyntaxFactsService syntaxFacts,
            CancellationToken cancellationToken)
        {
            switch (diagnosticId)
            {
            case IDEDiagnosticIds.ExpressionValueIsUnusedDiagnosticId:
                FixAllExpressionValueIsUnusedDiagnostics(diagnostics, semanticModel, root,
                                                         preference, nameGenerator, editor, syntaxFacts, cancellationToken);
                break;

            case IDEDiagnosticIds.ValueAssignedIsUnusedDiagnosticId:
                await FixAllValueAssignedIsUnusedDiagnosticsAsync(diagnostics, document, semanticModel, root, containingMemberDeclaration,
                                                                  preference, removeAssignments, nameGenerator, editor, syntaxFacts, cancellationToken).ConfigureAwait(false);

                break;

            default:
                throw ExceptionUtilities.Unreachable;
            }
        }
Esempio n. 2
0
        private async Task <SyntaxNode> PostProcessDocumentAsync(
            Document document,
            SyntaxNode currentRoot,
            string diagnosticId,
            UnusedValuePreference preference,
            CancellationToken cancellationToken)
        {
            // If we added discard assignments, replace all discard variable declarations in
            // this method with discard assignments, i.e. "var _ = M();" is replaced with "_ = M();"
            // This is done to prevent compiler errors where the existing method has a discard
            // variable declaration at a line following the one we added a discard assignment in our fix.
            if (preference == UnusedValuePreference.DiscardVariable)
            {
                currentRoot = await PostProcessDocumentCoreAsync(
                    ReplaceDiscardDeclarationsWithAssignmentsAsync, currentRoot, document, cancellationToken).ConfigureAwait(false);
            }

            // If we added new variable declaration statements, move these as close as possible to their
            // first reference site.
            if (NeedsToMoveNewLocalDeclarationsNearReference(diagnosticId))
            {
                currentRoot = await PostProcessDocumentCoreAsync(
                    AdjustLocalDeclarationsAsync, currentRoot, document, cancellationToken).ConfigureAwait(false);
            }

            return(currentRoot);
        }
Esempio n. 3
0
        private IEnumerable <IGrouping <SyntaxNode, Diagnostic> > GetDiagnosticsGroupedByMember(
            ImmutableArray <Diagnostic> diagnostics,
            ISyntaxFactsService syntaxFacts,
            SyntaxNode root,
            out string diagnosticId,
            out UnusedValuePreference preference,
            out bool removeAssignments)
        {
            diagnosticId = diagnostics[0].Id;
            var success = AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.TryGetUnusedValuePreference(diagnostics[0], out preference);

            Debug.Assert(success);
            removeAssignments = AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.GetIsRemovableAssignmentDiagnostic(diagnostics[0]);
#if DEBUG
            foreach (var diagnostic in diagnostics)
            {
                Debug.Assert(diagnosticId == diagnostic.Id);
                Debug.Assert(AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.TryGetUnusedValuePreference(diagnostic, out var diagnosticPreference) &&
                             diagnosticPreference == preference);
                Debug.Assert(removeAssignments == AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.GetIsRemovableAssignmentDiagnostic(diagnostic));
            }
#endif

            return(GetDiagnosticsGroupedByMember(diagnostics, syntaxFacts, root));
        }
Esempio n. 4
0
        public static bool TryGetUnusedValuePreference(
            Diagnostic diagnostic,
            out UnusedValuePreference preference
            )
        {
            if (
                diagnostic.Properties != null &&
                diagnostic.Properties.TryGetValue(
                    UnusedValuePreferenceKey,
                    out var preferenceString
                    )
                )
            {
                switch (preferenceString)
                {
                case nameof(UnusedValuePreference.DiscardVariable):
                    preference = UnusedValuePreference.DiscardVariable;
                    return(true);

                case nameof(UnusedValuePreference.UnusedLocalVariable):
                    preference = UnusedValuePreference.UnusedLocalVariable;
                    return(true);
                }
            }

            preference = default;
            return(false);
        }
Esempio n. 5
0
        private void FixAllExpressionValueIsUnusedDiagnostics(
            IOrderedEnumerable <Diagnostic> diagnostics,
            SemanticModel semanticModel,
            SyntaxNode root,
            UnusedValuePreference preference,
            UniqueVariableNameGenerator nameGenerator,
            SyntaxEditor editor,
            ISyntaxFactsService syntaxFacts,
            CancellationToken cancellationToken)
        {
            // This method applies the code fix for diagnostics reported for expression statement dropping values.
            // We replace each flagged expression statement with an assignment to a discard variable or a new unused local,
            // based on the user's preference.

            foreach (var diagnostic in diagnostics)
            {
                var expressionStatement = root.FindNode(diagnostic.Location.SourceSpan).FirstAncestorOrSelf <TExpressionStatementSyntax>();
                if (expressionStatement == null)
                {
                    continue;
                }

                var expression = syntaxFacts.GetExpressionOfExpressionStatement(expressionStatement);
                switch (preference)
                {
                case UnusedValuePreference.DiscardVariable:
                    Debug.Assert(semanticModel.Language != LanguageNames.VisualBasic);
                    var discardAssignmentExpression = (TExpressionSyntax)editor.Generator.AssignmentStatement(
                        left: editor.Generator.IdentifierName(AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.DiscardVariableName),
                        right: expression.WithoutTrivia())
                                                      .WithTriviaFrom(expression)
                                                      .WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation);
                    editor.ReplaceNode(expression, discardAssignmentExpression);
                    break;

                case UnusedValuePreference.UnusedLocalVariable:
                    // Add Simplifier annotation so that 'var'/explicit type is correctly added based on user options.
                    var localDecl = editor.Generator.LocalDeclarationStatement(
                        name: nameGenerator.GenerateUniqueNameAtSpanStart(expressionStatement),
                        initializer: expression.WithoutLeadingTrivia())
                                    .WithTriviaFrom(expressionStatement)
                                    .WithAdditionalAnnotations(Simplifier.Annotation, Formatter.Annotation);
                    editor.ReplaceNode(expressionStatement, localDecl);
                    break;
                }
            }
        }
            public Options(
                UnusedValuePreference unusedValueExpressionStatementPreference,
                ReportDiagnostic unusedValueExpressionStatementSeverity,
                UnusedValuePreference unusedValueAssignmentPreference,
                ReportDiagnostic unusedValueAssignmentSeverity,
                UnusedParametersPreference unusedParametersPreference,
                ReportDiagnostic unusedParametersSeverity)
            {
                Debug.Assert(unusedValueExpressionStatementSeverity != ReportDiagnostic.Suppress ||
                             unusedValueAssignmentSeverity != ReportDiagnostic.Suppress ||
                             unusedParametersSeverity != ReportDiagnostic.Suppress);

                UnusedValueExpressionStatementPreference = unusedValueExpressionStatementPreference;
                UnusedValueExpressionStatementSeverity   = unusedValueExpressionStatementSeverity;
                UnusedValueAssignmentPreference          = unusedValueAssignmentPreference;
                UnusedValueAssignmentSeverity            = unusedValueAssignmentSeverity;
                _unusedParametersPreference = unusedParametersPreference;
                _unusedParametersSeverity   = unusedParametersSeverity;
            }
Esempio n. 7
0
        private async Task FixAllValueAssignedIsUnusedDiagnosticsAsync(
            IOrderedEnumerable <Diagnostic> diagnostics,
            Document document,
            SemanticModel semanticModel,
            SyntaxNode root,
            SyntaxNode containingMemberDeclaration,
            UnusedValuePreference preference,
            bool removeAssignments,
            UniqueVariableNameGenerator nameGenerator,
            SyntaxEditor editor,
            ISyntaxFactsService syntaxFacts,
            CancellationToken cancellationToken)
        {
            // This method applies the code fix for diagnostics reported for unused value assignments to local/parameter.
            // The actual code fix depends on whether or not the right hand side of the assignment has side effects.
            // For example, if the right hand side is a constant or a reference to a local/parameter, then it has no side effects.
            // The lack of side effects is indicated by the "removeAssignments" parameter for this function.

            // If the right hand side has no side effects, then we can replace the assignments with variable declarations that have no initializer
            // or completely remove the statement.
            // If the right hand side does have side effects, we replace the identifier token for unused value assignment with
            // a new identifier token (either discard '_' or new unused local variable name).

            // For both the above cases, if the original diagnostic was reported on a local declaration, i.e. redundant initialization
            // at declaration, then we also add a new variable declaration statement without initializer for this local.

            var nodeReplacementMap = PooledDictionary <SyntaxNode, SyntaxNode> .GetInstance();

            var nodesToRemove = PooledHashSet <SyntaxNode> .GetInstance();

            var nodesToAdd = PooledHashSet <(TLocalDeclarationStatementSyntax declarationStatement, SyntaxNode node)> .GetInstance();

            // Indicates if the node's trivia was processed.
            var processedNodes = PooledHashSet <SyntaxNode> .GetInstance();

            var candidateDeclarationStatementsForRemoval = PooledHashSet <TLocalDeclarationStatementSyntax> .GetInstance();

            var hasAnyUnusedLocalAssignment = false;

            try
            {
                foreach (var(node, isUnusedLocalAssignment) in GetNodesToFix())
                {
                    hasAnyUnusedLocalAssignment |= isUnusedLocalAssignment;

                    var declaredLocal = semanticModel.GetDeclaredSymbol(node, cancellationToken) as ILocalSymbol;
                    if (declaredLocal == null && node.Parent is TCatchStatementSyntax)
                    {
                        declaredLocal = semanticModel.GetDeclaredSymbol(node.Parent, cancellationToken) as ILocalSymbol;
                    }

                    string newLocalNameOpt = null;
                    if (removeAssignments)
                    {
                        // Removable assignment or initialization, such that right hand side has no side effects.
                        if (declaredLocal != null)
                        {
                            // Redundant initialization.
                            // For example, "int a = 0;"
                            var variableDeclarator = node.FirstAncestorOrSelf <TVariableDeclaratorSyntax>();
                            Debug.Assert(variableDeclarator != null);
                            nodesToRemove.Add(variableDeclarator);

                            // Local declaration statement containing the declarator might be a candidate for removal if all its variables get marked for removal.
                            candidateDeclarationStatementsForRemoval.Add(variableDeclarator.GetAncestor <TLocalDeclarationStatementSyntax>());
                        }
                        else
                        {
                            // Redundant assignment or increment/decrement.
                            if (syntaxFacts.IsOperandOfIncrementOrDecrementExpression(node))
                            {
                                // For example, C# increment operation "a++;"
                                Debug.Assert(node.Parent.Parent is TExpressionStatementSyntax);
                                nodesToRemove.Add(node.Parent.Parent);
                            }
                            else
                            {
                                Debug.Assert(syntaxFacts.IsLeftSideOfAnyAssignment(node));

                                if (node.Parent is TStatementSyntax)
                                {
                                    // For example, VB simple assignment statement "a = 0"
                                    nodesToRemove.Add(node.Parent);
                                }
                                else if (node.Parent is TExpressionSyntax && node.Parent.Parent is TExpressionStatementSyntax)
                                {
                                    // For example, C# simple assignment statement "a = 0;"
                                    nodesToRemove.Add(node.Parent.Parent);
                                }
                                else
                                {
                                    // For example, C# nested assignment statement "a = b = 0;", where assignment to 'b' is redundant.
                                    // We replace the node with "a = 0;"
                                    nodeReplacementMap.Add(node.Parent, syntaxFacts.GetRightHandSideOfAssignment(node.Parent));
                                }
                            }
                        }
                    }
                    else
                    {
                        // Value initialization/assignment where the right hand side may have side effects,
                        // and hence needs to be preserved in fixed code.
                        // For example, "x = MethodCall();" is replaced with "_ = MethodCall();" or "var unused = MethodCall();"

                        // Replace the flagged variable's indentifier token with new named, based on user's preference.
                        var newNameToken = preference == UnusedValuePreference.DiscardVariable
                            ? editor.Generator.Identifier(AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.DiscardVariableName)
                            : nameGenerator.GenerateUniqueNameAtSpanStart(node);
                        newLocalNameOpt = newNameToken.ValueText;
                        var newNameNode = TryUpdateNameForFlaggedNode(node, newNameToken);
                        if (newNameNode == null)
                        {
                            continue;
                        }

                        // Is this is compound assignment?
                        if (syntaxFacts.IsLeftSideOfAnyAssignment(node) && !syntaxFacts.IsLeftSideOfAssignment(node))
                        {
                            // 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();"
                            nodeReplacementMap.Add(node.Parent, GetReplacementNodeForCompoundAssignment(node.Parent, newNameNode, editor, syntaxFacts));
                        }
                        else
                        {
                            nodeReplacementMap.Add(node, newNameNode);
                        }
                    }

                    if (declaredLocal != null)
                    {
                        // We have a dead initialization for a local declaration.
                        // Introduce a new local declaration statement without an initializer for this local.
                        var declarationStatement = CreateLocalDeclarationStatement(declaredLocal.Type, declaredLocal.Name);
                        if (isUnusedLocalAssignment)
                        {
                            declarationStatement = declarationStatement.WithAdditionalAnnotations(s_unusedLocalDeclarationAnnotation);
                        }

                        nodesToAdd.Add((declarationStatement, node));
                    }
                    else
                    {
                        // We have a dead assignment to a local/parameter, which is not at the declaration site.
                        // Create a new local declaration for the unused local if both following conditions are met:
                        //  1. User prefers unused local variables for unused value assignment AND
                        //  2. Assignment value has side effects and hence cannot be removed.
                        if (preference == UnusedValuePreference.UnusedLocalVariable && !removeAssignments)
                        {
                            var type = semanticModel.GetTypeInfo(node, cancellationToken).Type;
                            Debug.Assert(type != null);
                            Debug.Assert(newLocalNameOpt != null);
                            var declarationStatement = CreateLocalDeclarationStatement(type, newLocalNameOpt);
                            nodesToAdd.Add((declarationStatement, node));
                        }
                    }
                }

                // Process candidate declaration statements for removal.
                foreach (var localDeclarationStatement in candidateDeclarationStatementsForRemoval)
                {
                    // If all the variable declarators for the local declaration statement are being removed,
                    // we can remove the entire local declaration statement.
                    if (ShouldRemoveStatement(localDeclarationStatement, out var variables))
                    {
                        nodesToRemove.Add(localDeclarationStatement);
                        nodesToRemove.RemoveRange(variables);
                    }
                }

                foreach (var nodeToAdd in nodesToAdd)
                {
                    InsertLocalDeclarationStatement(nodeToAdd.declarationStatement, nodeToAdd.node);
                }

                if (hasAnyUnusedLocalAssignment)
                {
                    // Local declaration statements with no initializer, but non-zero references are candidates for removal
                    // if the code fix removes all these references.
                    // We annotate such declaration statements with no initializer abd non-zero references here
                    // and remove them in post process document pass later, if the code fix did remove all these references.
                    foreach (var localDeclarationStatement in containingMemberDeclaration.DescendantNodes().OfType <TLocalDeclarationStatementSyntax>())
                    {
                        var variables = syntaxFacts.GetVariablesOfLocalDeclarationStatement(localDeclarationStatement);
                        if (variables.Count == 1 &&
                            syntaxFacts.GetInitializerOfVariableDeclarator(variables[0]) == null &&
                            !(await IsLocalDeclarationWithNoReferencesAsync(localDeclarationStatement, document, cancellationToken).ConfigureAwait(false)))
                        {
                            nodeReplacementMap.Add(localDeclarationStatement, localDeclarationStatement.WithAdditionalAnnotations(s_existingLocalDeclarationWithoutInitializerAnnotation));
                        }
                    }
                }

                foreach (var node in nodesToRemove)
                {
                    var removeOptions = SyntaxGenerator.DefaultRemoveOptions;
                    // If the leading trivia was not added to a new node, process it now.
                    if (!processedNodes.Contains(node))
                    {
                        // Don't keep trivia if the node is part of a multiple declaration statement.
                        // e.g. int x = 0, y = 0, z = 0; any white space left behind can cause problems if the declaration gets split apart.
                        var containingDeclaration = node.GetAncestor <TLocalDeclarationStatementSyntax>();
                        if (containingDeclaration != null && candidateDeclarationStatementsForRemoval.Contains(containingDeclaration))
                        {
                            removeOptions = SyntaxRemoveOptions.KeepNoTrivia;
                        }
                        else
                        {
                            removeOptions |= SyntaxRemoveOptions.KeepLeadingTrivia;
                        }
                    }
                    editor.RemoveNode(node, removeOptions);
                }

                foreach (var kvp in nodeReplacementMap)
                {
                    editor.ReplaceNode(kvp.Key, kvp.Value.WithAdditionalAnnotations(Formatter.Annotation));
                }
            }
            finally
            {
                nodeReplacementMap.Free();
                nodesToRemove.Free();
                nodesToAdd.Free();
                processedNodes.Free();
            }

            return;

            // Local functions.
            IEnumerable <(SyntaxNode node, bool isUnusedLocalAssignment)> GetNodesToFix()
            {
                foreach (var diagnostic in diagnostics)
                {
                    var node = root.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
                    var isUnusedLocalAssignment = AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.GetIsUnusedLocalDiagnostic(diagnostic);
                    yield return(node, isUnusedLocalAssignment);
                }
            }

            // Mark generated local declaration statement with:
            //  1. "s_newLocalDeclarationAnnotation" for post processing in "MoveNewLocalDeclarationsNearReference" below.
            //  2. Simplifier annotation so that 'var'/explicit type is correctly added based on user options.
            TLocalDeclarationStatementSyntax CreateLocalDeclarationStatement(ITypeSymbol type, string name)
            => (TLocalDeclarationStatementSyntax)editor.Generator.LocalDeclarationStatement(type, name)
            .WithLeadingTrivia(editor.Generator.ElasticCarriageReturnLineFeed)
            .WithAdditionalAnnotations(s_newLocalDeclarationStatementAnnotation, Simplifier.Annotation);

            void InsertLocalDeclarationStatement(TLocalDeclarationStatementSyntax declarationStatement, SyntaxNode node)
            {
                // Find the correct place to insert the given declaration statement based on the node's ancestors.
                var insertionNode = node.FirstAncestorOrSelf <SyntaxNode>(n => n.Parent is TSwitchCaseBlockSyntax ||
                                                                          syntaxFacts.IsExecutableBlock(n.Parent) &&
                                                                          !(n is TCatchStatementSyntax) &&
                                                                          !(n is TCatchBlockSyntax));

                if (insertionNode is TSwitchCaseLabelOrClauseSyntax)
                {
                    InsertAtStartOfSwitchCaseBlockForDeclarationInCaseLabelOrClause(insertionNode.GetAncestor <TSwitchCaseBlockSyntax>(), editor, declarationStatement);
                }
                else if (insertionNode is TStatementSyntax)
                {
                    // If the insertion node is being removed, keep the leading trivia with the new declaration.
                    if (nodesToRemove.Contains(insertionNode) && !processedNodes.Contains(insertionNode))
                    {
                        declarationStatement = declarationStatement.WithLeadingTrivia(insertionNode.GetLeadingTrivia());
                        // Mark the node as processed so that the trivia only gets added once.
                        processedNodes.Add(insertionNode);
                    }
                    editor.InsertBefore(insertionNode, declarationStatement);
                }
            }

            bool ShouldRemoveStatement(TLocalDeclarationStatementSyntax localDeclarationStatement, out SeparatedSyntaxList <SyntaxNode> variables)
            {
                Debug.Assert(removeAssignments);

                // We should remove the entire local declaration statement if all its variables are marked for removal.
                variables = syntaxFacts.GetVariablesOfLocalDeclarationStatement(localDeclarationStatement);
                foreach (var variable in variables)
                {
                    if (!nodesToRemove.Contains(variable))
                    {
                        return(false);
                    }
                }

                return(true);
            }
        }
Esempio n. 8
0
 private static string GetEquivalenceKey(UnusedValuePreference preference, bool isRemovableAssignment)
 => preference.ToString() + isRemovableAssignment;
Esempio n. 9
0
        private static string GetUnusedExpressionAssignmentPreferenceEditorConfigString(CodeStyleOption <UnusedValuePreference> option, UnusedValuePreference defaultPreference)
        {
            Debug.Assert(s_unusedExpressionAssignmentPreferenceMap.ContainsValue(option.Value));
            var value = s_unusedExpressionAssignmentPreferenceMap.GetKeyOrDefault(option.Value) ?? s_unusedExpressionAssignmentPreferenceMap.GetKeyOrDefault(defaultPreference);

            return(option.Notification == null ? value : $"{value}:{option.Notification.ToEditorConfigString()}");
        }