File: CodeFixes\Suppression\CSharpSuppressionCodeFixProvider.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// 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;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.AddImport;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CodeFixes.Suppression;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Simplification;
 
namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.Suppression;
 
using static CSharpSyntaxTokens;
using static SyntaxFactory;
 
[ExportConfigurationFixProvider(PredefinedConfigurationFixProviderNames.Suppression, LanguageNames.CSharp), Shared]
internal class CSharpSuppressionCodeFixProvider : AbstractSuppressionCodeFixProvider
{
    [ImportingConstructor]
    [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
    public CSharpSuppressionCodeFixProvider()
    {
    }
 
    protected override SyntaxTriviaList CreatePragmaRestoreDirectiveTrivia(Diagnostic diagnostic, Func<SyntaxNode, CancellationToken, SyntaxNode> formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine, CancellationToken cancellationToken)
    {
        var restoreKeyword = RestoreKeyword;
        return CreatePragmaDirectiveTrivia(restoreKeyword, diagnostic, formatNode, needsLeadingEndOfLine, needsTrailingEndOfLine, cancellationToken);
    }
 
    protected override SyntaxTriviaList CreatePragmaDisableDirectiveTrivia(
        Diagnostic diagnostic, Func<SyntaxNode, CancellationToken, SyntaxNode> formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine, CancellationToken cancellationToken)
    {
        var disableKeyword = DisableKeyword;
        return CreatePragmaDirectiveTrivia(disableKeyword, diagnostic, formatNode, needsLeadingEndOfLine, needsTrailingEndOfLine, cancellationToken);
    }
 
    private static SyntaxTriviaList CreatePragmaDirectiveTrivia(
        SyntaxToken disableOrRestoreKeyword, Diagnostic diagnostic, Func<SyntaxNode, CancellationToken, SyntaxNode> formatNode, bool needsLeadingEndOfLine, bool needsTrailingEndOfLine, CancellationToken cancellationToken)
    {
        var diagnosticId = GetOrMapDiagnosticId(diagnostic, out var includeTitle);
        var id = IdentifierName(diagnosticId);
        var ids = new SeparatedSyntaxList<ExpressionSyntax>().Add(id);
        var pragmaDirective = PragmaWarningDirectiveTrivia(disableOrRestoreKeyword, ids, true);
        pragmaDirective = (PragmaWarningDirectiveTriviaSyntax)formatNode(pragmaDirective, cancellationToken);
        var pragmaDirectiveTrivia = Trivia(pragmaDirective);
        var endOfLineTrivia = CarriageReturnLineFeed;
        var triviaList = TriviaList(pragmaDirectiveTrivia);
 
        var title = includeTitle ? diagnostic.Descriptor.Title.ToString(CultureInfo.CurrentUICulture) : null;
        if (!string.IsNullOrWhiteSpace(title))
        {
            var titleComment = Comment(string.Format(" // {0}", title)).WithAdditionalAnnotations(Formatter.Annotation);
            triviaList = triviaList.Add(titleComment);
        }
 
        if (needsLeadingEndOfLine)
        {
            triviaList = triviaList.Insert(0, endOfLineTrivia);
        }
 
        if (needsTrailingEndOfLine)
        {
            triviaList = triviaList.Add(endOfLineTrivia);
        }
 
        return triviaList;
    }
 
    protected override string DefaultFileExtension => ".cs";
 
    protected override string SingleLineCommentStart => "//";
 
    protected override bool IsAttributeListWithAssemblyAttributes(SyntaxNode node)
    {
        return node is AttributeListSyntax attributeList &&
            attributeList.Target != null &&
            attributeList.Target.Identifier.Kind() == SyntaxKind.AssemblyKeyword;
    }
 
    protected override bool IsEndOfLine(SyntaxTrivia trivia)
        => trivia.Kind() is SyntaxKind.EndOfLineTrivia or SyntaxKind.SingleLineDocumentationCommentTrivia;
 
    protected override bool IsEndOfFileToken(SyntaxToken token)
        => token.Kind() == SyntaxKind.EndOfFileToken;
 
    protected override SyntaxNode AddGlobalSuppressMessageAttribute(
        SyntaxNode newRoot,
        ISymbol targetSymbol,
        INamedTypeSymbol suppressMessageAttribute,
        Diagnostic diagnostic,
        SolutionServices services,
        SyntaxFormattingOptions options,
        IAddImportsService addImportsService,
        CancellationToken cancellationToken)
    {
        var compilationRoot = (CompilationUnitSyntax)newRoot;
        var isFirst = !compilationRoot.AttributeLists.Any();
 
        var attributeName = suppressMessageAttribute.GenerateNameSyntax()
                                                    .WithAdditionalAnnotations(Simplifier.AddImportsAnnotation);
 
        compilationRoot = compilationRoot.AddAttributeLists(
            CreateAttributeList(
                targetSymbol,
                attributeName,
                diagnostic,
                isAssemblyAttribute: true,
                leadingTrivia: default));
 
        if (isFirst && !newRoot.HasLeadingTrivia)
            compilationRoot = compilationRoot.WithLeadingTrivia(Comment(GlobalSuppressionsFileHeaderComment));
 
        return compilationRoot;
    }
 
    protected override SyntaxNode AddLocalSuppressMessageAttribute(
        SyntaxNode targetNode, ISymbol targetSymbol, INamedTypeSymbol suppressMessageAttribute, Diagnostic diagnostic)
    {
        var memberNode = (MemberDeclarationSyntax)targetNode;
 
        SyntaxTriviaList leadingTriviaForAttributeList;
        if (!memberNode.GetAttributes().Any())
        {
            leadingTriviaForAttributeList = memberNode.GetLeadingTrivia();
            memberNode = memberNode.WithoutLeadingTrivia();
        }
        else
        {
            leadingTriviaForAttributeList = default;
        }
 
        var attributeName = suppressMessageAttribute.GenerateNameSyntax();
        var attributeList = CreateAttributeList(
            targetSymbol, attributeName, diagnostic, isAssemblyAttribute: false, leadingTrivia: leadingTriviaForAttributeList);
        return memberNode.AddAttributeLists(attributeList);
    }
 
    private static AttributeListSyntax CreateAttributeList(
        ISymbol targetSymbol,
        NameSyntax attributeName,
        Diagnostic diagnostic,
        bool isAssemblyAttribute,
        SyntaxTriviaList leadingTrivia)
    {
        var attributeArguments = CreateAttributeArguments(targetSymbol, diagnostic, isAssemblyAttribute);
 
        var attributes = new SeparatedSyntaxList<AttributeSyntax>()
            .Add(Attribute(attributeName, attributeArguments));
 
        AttributeListSyntax attributeList;
        if (isAssemblyAttribute)
        {
            var targetSpecifier = AttributeTargetSpecifier(AssemblyKeyword);
            attributeList = AttributeList(targetSpecifier, attributes);
        }
        else
        {
            attributeList = AttributeList(attributes);
        }
 
        return attributeList.WithLeadingTrivia(leadingTrivia);
    }
 
    private static AttributeArgumentListSyntax CreateAttributeArguments(ISymbol targetSymbol, Diagnostic diagnostic, bool isAssemblyAttribute)
    {
        // SuppressMessage("Rule Category", "Rule Id", Justification = nameof(Justification), Scope = nameof(Scope), Target = nameof(Target))
        var category = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(diagnostic.Descriptor.Category));
        var categoryArgument = AttributeArgument(category);
 
        var title = diagnostic.Descriptor.Title.ToString(CultureInfo.CurrentUICulture);
        var ruleIdText = string.IsNullOrWhiteSpace(title) ? diagnostic.Id : string.Format("{0}:{1}", diagnostic.Id, title);
        var ruleId = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(ruleIdText));
        var ruleIdArgument = AttributeArgument(ruleId);
 
        var justificationExpr = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(FeaturesResources.Pending));
        var justificationArgument = AttributeArgument(NameEquals("Justification"), nameColon: null, expression: justificationExpr);
 
        var attributeArgumentList = AttributeArgumentList().AddArguments(categoryArgument, ruleIdArgument, justificationArgument);
 
        if (isAssemblyAttribute)
        {
            var scopeString = GetScopeString(targetSymbol.Kind);
            if (scopeString != null)
            {
                var scopeExpr = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(scopeString));
                var scopeArgument = AttributeArgument(NameEquals("Scope"), nameColon: null, expression: scopeExpr);
 
                var targetString = GetTargetString(targetSymbol);
                var targetExpr = LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(targetString));
                var targetArgument = AttributeArgument(NameEquals("Target"), nameColon: null, expression: targetExpr);
 
                attributeArgumentList = attributeArgumentList.AddArguments(scopeArgument, targetArgument);
            }
        }
 
        return attributeArgumentList;
    }
 
    protected override bool IsSingleAttributeInAttributeList(SyntaxNode attribute)
    {
        if (attribute is AttributeSyntax attributeSyntax)
        {
            return attributeSyntax.Parent is AttributeListSyntax attributeList && attributeList.Attributes.Count == 1;
        }
 
        return false;
    }
 
    protected override bool IsAnyPragmaDirectiveForId(SyntaxTrivia trivia, string id, out bool enableDirective, out bool hasMultipleIds)
    {
        if (trivia.Kind() == SyntaxKind.PragmaWarningDirectiveTrivia)
        {
            var pragmaWarning = (PragmaWarningDirectiveTriviaSyntax)trivia.GetStructure();
            enableDirective = pragmaWarning.DisableOrRestoreKeyword.Kind() == SyntaxKind.RestoreKeyword;
            hasMultipleIds = pragmaWarning.ErrorCodes.Count > 1;
            return pragmaWarning.ErrorCodes.Any(n => n.ToString() == id);
        }
 
        enableDirective = false;
        hasMultipleIds = false;
        return false;
    }
 
    protected override SyntaxTrivia TogglePragmaDirective(SyntaxTrivia trivia)
    {
        var pragmaWarning = (PragmaWarningDirectiveTriviaSyntax)trivia.GetStructure();
        var currentKeyword = pragmaWarning.DisableOrRestoreKeyword;
        var toggledKeywordKind = currentKeyword.Kind() == SyntaxKind.DisableKeyword ? SyntaxKind.RestoreKeyword : SyntaxKind.DisableKeyword;
        var toggledToken = Token(currentKeyword.LeadingTrivia, toggledKeywordKind, currentKeyword.TrailingTrivia);
        var newPragmaWarning = pragmaWarning.WithDisableOrRestoreKeyword(toggledToken);
        return Trivia(newPragmaWarning);
    }
 
    protected override SyntaxNode GetContainingStatement(SyntaxToken token)
        // If we can't get a containing statement, such as for expression bodied members, then
        // return the arrow clause instead
        => (SyntaxNode)token.GetAncestor<StatementSyntax>() ?? token.GetAncestor<ArrowExpressionClauseSyntax>();
 
    protected override bool TokenHasTrailingLineContinuationChar(SyntaxToken token)
        => false;
}