public static void CreateAndRegisterActions(
                CompilationStartAnalysisContext context,
                AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer analyzer)
            {
                var attributeSetForMethodsToIgnore = ImmutableHashSet.CreateRange(GetAttributesForMethodsToIgnore(context.Compilation).WhereNotNull());
                var eventsArgType = context.Compilation.EventArgsType();
                var deserializationConstructorCheck = new DeserializationConstructorCheck(context.Compilation);
                var iCustomMarshaler = context.Compilation.GetTypeByMetadataName(typeof(ICustomMarshaler).FullName !);

                context.RegisterSymbolStartAction(symbolStartContext =>
                {
                    if (HasSyntaxErrors((INamedTypeSymbol)symbolStartContext.Symbol, symbolStartContext.CancellationToken))
                    {
                        // Bail out on syntax errors.
                        return;
                    }

                    // Create a new SymbolStartAnalyzer instance for every named type symbol
                    // to ensure there is no shared state (such as identified unused parameters within the type),
                    // as that would lead to duplicate diagnostics being reported from symbol end action callbacks
                    // for unrelated named types.
                    var symbolAnalyzer = new SymbolStartAnalyzer(analyzer, eventsArgType, attributeSetForMethodsToIgnore, deserializationConstructorCheck, iCustomMarshaler);
                    symbolAnalyzer.OnSymbolStart(symbolStartContext);
                }, SymbolKind.NamedType);

                return;
Example #2
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));
        }
Example #3
0
            public static void CreateAndRegisterActions(
                CompilationStartAnalysisContext context,
                AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer analyzer)
            {
                var attributeSetForMethodsToIgnore = ImmutableHashSet.CreateRange(GetAttributesForMethodsToIgnore(context.Compilation).WhereNotNull());
                var eventsArgType  = context.Compilation.EventArgsType();
                var symbolAnalyzer = new SymbolStartAnalyzer(analyzer, eventsArgType, attributeSetForMethodsToIgnore);

                context.RegisterSymbolStartAction(symbolAnalyzer.OnSymbolStart, SymbolKind.NamedType);
            }
Example #4
0
        private static string GetEquivalenceKey(Diagnostic diagnostic)
        {
            if (!AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.TryGetUnusedValuePreference(diagnostic, out var preference))
            {
                return(string.Empty);
            }

            var isRemovableAssignment = AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.GetIsRemovableAssignmentDiagnostic(diagnostic);

            return(GetEquivalenceKey(preference, isRemovableAssignment));
        }
            public SymbolStartAnalyzer(
                AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer compilationAnalyzer,
                INamedTypeSymbol eventArgsTypeOpt,
                ImmutableHashSet <INamedTypeSymbol> attributeSetForMethodsToIgnore)
            {
                _compilationAnalyzer = compilationAnalyzer;

                _eventArgsTypeOpt = eventArgsTypeOpt;
                _attributeSetForMethodsToIgnore = attributeSetForMethodsToIgnore;
                _unusedParameters       = new ConcurrentDictionary <IParameterSymbol, bool>();
                _methodsUsedAsDelegates = new ConcurrentDictionary <IMethodSymbol, bool>();
            }
Example #6
0
        public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var diagnostic = context.Diagnostics[0];

            if (!AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.TryGetUnusedValuePreference(diagnostic, out var preference))
            {
                return(Task.CompletedTask);
            }

            var isRemovableAssignment = AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.GetIsRemovableAssignmentDiagnostic(diagnostic);

            string title;

            if (isRemovableAssignment)
            {
                // Recommend removing the redundant constant value assignment.
                title = FeaturesResources.Remove_redundant_assignment;
            }
            else
            {
                // Recommend using discard/unused local for redundant non-constant assignment.
                switch (preference)
                {
                case UnusedValuePreference.DiscardVariable:
                    if (IsForEachIterationVariableDiagnostic(diagnostic, context.Document, context.CancellationToken))
                    {
                        // Do not offer a fix to replace unused foreach iteration variable with discard.
                        // User should probably replace it with a for loop based on the collection length.
                        return(Task.CompletedTask);
                    }

                    title = FeaturesResources.Use_discard_underscore;
                    break;

                case UnusedValuePreference.UnusedLocalVariable:
                    title = FeaturesResources.Use_discarded_local;
                    break;

                default:
                    return(Task.CompletedTask);
                }
            }

            context.RegisterCodeFix(
                new MyCodeAction(
                    title,
                    c => FixAsync(context.Document, diagnostic, c),
                    equivalenceKey: GetEquivalenceKey(preference, isRemovableAssignment)),
                diagnostic);

            return(Task.CompletedTask);
        }
            public SymbolStartAnalyzer(
                AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer compilationAnalyzer,
                INamedTypeSymbol eventArgsTypeOpt,
                ImmutableHashSet <INamedTypeSymbol> attributeSetForMethodsToIgnore,
                DeserializationConstructorCheck deserializationConstructorCheck,
                INamedTypeSymbol iCustomMarshaler)
            {
                _compilationAnalyzer = compilationAnalyzer;

                _eventArgsTypeOpt = eventArgsTypeOpt;
                _attributeSetForMethodsToIgnore  = attributeSetForMethodsToIgnore;
                _deserializationConstructorCheck = deserializationConstructorCheck;
                _unusedParameters       = new ConcurrentDictionary <IParameterSymbol, bool>();
                _methodsUsedAsDelegates = new ConcurrentDictionary <IMethodSymbol, bool>();
                _iCustomMarshaler       = iCustomMarshaler;
            }
            public static void CreateAndRegisterActions(
                CompilationStartAnalysisContext context,
                AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer analyzer)
            {
                var attributeSetForMethodsToIgnore = ImmutableHashSet.CreateRange(GetAttributesForMethodsToIgnore(context.Compilation).WhereNotNull());
                var eventsArgType = context.Compilation.EventArgsType();

                context.RegisterSymbolStartAction(symbolStartContext =>
                {
                    // Create a new SymbolStartAnalyzer instance for every named type symbol
                    // to ensure there is no shared state (such as identified unused parameters within the type),
                    // as that would lead to duplicate diagnostics being reported from symbol end action callbacks
                    // for unrelated named types.
                    var symbolAnalyzer = new SymbolStartAnalyzer(analyzer, eventsArgType, attributeSetForMethodsToIgnore);
                    symbolAnalyzer.OnSymbolStart(symbolStartContext);
                }, SymbolKind.NamedType);
            }
Example #9
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);
            }
        }
        public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var diagnostic = context.Diagnostics[0];

            if (!AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.TryGetUnusedValuePreference(diagnostic, out var preference))
            {
                return;
            }

            var isRemovableAssignment = AbstractRemoveUnusedParametersAndValuesDiagnosticAnalyzer.GetIsRemovableAssignmentDiagnostic(diagnostic);

            string title;

            if (isRemovableAssignment)
            {
                // Recommend removing the redundant constant value assignment.
                title = FeaturesResources.Remove_redundant_assignment;
            }
            else
            {
                // Recommend using discard/unused local for redundant non-constant assignment.
                switch (preference)
                {
                case UnusedValuePreference.DiscardVariable:
                    if (IsForEachIterationVariableDiagnostic(diagnostic, context.Document, context.CancellationToken))
                    {
                        // Do not offer a fix to replace unused foreach iteration variable with discard.
                        // User should probably replace it with a for loop based on the collection length.
                        return;
                    }

                    title = FeaturesResources.Use_discard_underscore;

                    // Check if this is compound assignment which is not parented by an expression statement,
                    // for example "return x += M();" OR "=> x ??= new C();"
                    // If so, we will be replacing this compound assignment with the underlying binary operation.
                    // For the above examples, it will be "return x + M();" AND "=> x ?? new C();" respectively.
                    // For these cases, we want to show the title as "Remove redundant assignment" instead of "Use discard _".

                    var syntaxFacts = context.Document.GetLanguageService <ISyntaxFactsService>();
                    var root        = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

                    var node = root.FindNode(context.Span, getInnermostNodeForTie: true);
                    if (syntaxFacts.IsLeftSideOfCompoundAssignment(node) &&
                        !syntaxFacts.IsExpressionStatement(node.Parent))
                    {
                        title = FeaturesResources.Remove_redundant_assignment;
                    }

                    break;

                case UnusedValuePreference.UnusedLocalVariable:
                    title = FeaturesResources.Use_discarded_local;
                    break;

                default:
                    return;
                }
            }

            context.RegisterCodeFix(
                new MyCodeAction(
                    title,
                    c => FixAsync(context.Document, diagnostic, c),
                    equivalenceKey: GetEquivalenceKey(preference, isRemovableAssignment)),
                diagnostic);

            return;
        }