Ejemplo 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;
            }
        }
Ejemplo n.º 2
0
        protected sealed override async Task FixAllAsync(Document document, ImmutableArray <Diagnostic> diagnostics, SyntaxEditor editor, CancellationToken cancellationToken)
        {
            document = await PreprocessDocumentAsync(document, diagnostics, cancellationToken).ConfigureAwait(false);

            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

            var syntaxFacts   = document.GetLanguageService <ISyntaxFactsService>();
            var semanticFacts = document.GetLanguageService <ISemanticFactsService>();

            var originalEditor = editor;

            editor = new SyntaxEditor(root, editor.Generator);

            try
            {
                // We compute the code fix in two passes:
                //   1. The first pass groups the diagnostics to fix by containing member declaration and
                //      computes and applies the core code fixes. Grouping is done to ensure we choose
                //      the most appropriate name for new unused local declarations, which can clash
                //      with existing local declarations in the method body.
                //   2. Second pass (PostProcessDocumentAsync) performs additional syntax manipulations
                //      for the fixes produced from from first pass:
                //      a. Replace discard declarations, such as "var _ = M();" that conflict with newly added
                //         discard assignments, with discard assignments of the form "_ = M();"
                //      b. Move newly introduced local declaration statements closer to the local variable's
                //         first reference.

                // Get diagnostics grouped by member.
                var diagnosticsGroupedByMember = GetDiagnosticsGroupedByMember(diagnostics, syntaxFacts, root,
                                                                               out var diagnosticId, out var preference, out var removeAssignments);

                // First pass to compute and apply the core code fixes.
                var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

                foreach (var diagnosticsToFix in diagnosticsGroupedByMember)
                {
                    var orderedDiagnostics          = diagnosticsToFix.OrderBy(d => d.Location.SourceSpan.Start);
                    var containingMemberDeclaration = diagnosticsToFix.Key;
                    using (var nameGenerator = new UniqueVariableNameGenerator(containingMemberDeclaration, semanticModel, semanticFacts, cancellationToken))
                    {
                        await FixAllAsync(diagnosticId, orderedDiagnostics, document, semanticModel, root, containingMemberDeclaration, preference,
                                          removeAssignments, nameGenerator, editor, syntaxFacts, cancellationToken).ConfigureAwait(false);
                    }
                }

                // Second pass to post process the document.
                var currentRoot = editor.GetChangedRoot();
                var newRoot     = await PostProcessDocumentAsync(document, currentRoot,
                                                                 diagnosticId, preference, cancellationToken).ConfigureAwait(false);

                if (currentRoot != newRoot)
                {
                    editor.ReplaceNode(root, newRoot);
                }
            }
            finally
            {
                originalEditor.ReplaceNode(originalEditor.OriginalRoot, editor.GetChangedRoot());
            }
        }
Ejemplo n.º 3
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;
                }
            }
        }
Ejemplo n.º 4
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);
            }
        }
Ejemplo n.º 5
0
        public override void VisitConstructorDeclaration(IConstructorDeclaration decl, SST context)
        {
            _cancellationToken.ThrowIfCancellationRequested();

            var nameGen   = new UniqueVariableNameGenerator();
            var exprVisit = new ExpressionVisitor(nameGen, _marker);

            if (decl.DeclaredElement != null)
            {
                var methodName = decl.DeclaredElement.GetName <IMethodName>();

                var sstDecl = new MethodDeclaration
                {
                    Name         = methodName,
                    IsEntryPoint = _entryPoints.Contains(methodName)
                };
                context.Methods.Add(sstDecl);

                if (decl == _marker.AffectedNode)
                {
                    sstDecl.Body.Add(new ExpressionStatement {
                        Expression = new CompletionExpression()
                    });
                }

                if (decl.Initializer != null)
                {
                    var name = Names.UnknownMethod;

                    var substitution = decl.DeclaredElement.IdSubstitution;
                    var resolvedRef  = decl.Initializer.Reference.Resolve();
                    if (resolvedRef.DeclaredElement != null)
                    {
                        name = resolvedRef.DeclaredElement.GetName <IMethodName>(substitution);
                    }

                    var args = Lists.NewList <ISimpleExpression>();
                    foreach (var p in decl.Initializer.Arguments)
                    {
                        var expr = exprVisit.ToSimpleExpression(p.Value, sstDecl.Body);
                        args.Add(expr);
                    }

                    var varId = new VariableReference().Identifier; // default value
                    if (decl.Initializer.Instance != null)
                    {
                        var tokenType = decl.Initializer.Instance.GetTokenType();
                        var isThis    = CSharpTokenType.THIS_KEYWORD == tokenType;
                        var isBase    = CSharpTokenType.BASE_KEYWORD == tokenType;

                        varId = isThis ? "this" : isBase ? "base" : varId;
                    }

                    sstDecl.Body.Add(
                        new ExpressionStatement
                    {
                        Expression = new InvocationExpression
                        {
                            Reference = new VariableReference {
                                Identifier = varId
                            },
                            MethodName = name,
                            Parameters = args
                        }
                    });
                }

                if (!decl.IsAbstract)
                {
                    var bodyVisitor = new BodyVisitor(nameGen, _marker);

                    Execute.AndSupressExceptions(
                        delegate { decl.Accept(bodyVisitor, sstDecl.Body); });
                }
            }
        }
Ejemplo n.º 6
0
 public ExpressionVisitor(UniqueVariableNameGenerator nameGen, CompletionTargetMarker marker)
 {
     _nameGen = nameGen;
     _marker  = marker;
 }
Ejemplo n.º 7
0
 public StatementVisitor(UniqueVariableNameGenerator nameGen, CompletionTargetMarker marker)
 {
     _marker      = marker;
     _nameGen     = nameGen;
     _exprVisitor = new ExpressionVisitor(_nameGen, marker);
 }
Ejemplo n.º 8
0
 public ToAssignableReference(UniqueVariableNameGenerator varNameGenerator)
 {
 }