|
// 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;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.CodeFixes.Suppression
{
[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 = SyntaxFactory.Token(SyntaxKind.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 = SyntaxFactory.Token(SyntaxKind.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 = SyntaxFactory.IdentifierName(diagnosticId);
var ids = new SeparatedSyntaxList<ExpressionSyntax>().Add(id);
var pragmaDirective = SyntaxFactory.PragmaWarningDirectiveTrivia(disableOrRestoreKeyword, ids, true);
pragmaDirective = (PragmaWarningDirectiveTriviaSyntax)formatNode(pragmaDirective, cancellationToken);
var pragmaDirectiveTrivia = SyntaxFactory.Trivia(pragmaDirective);
var endOfLineTrivia = SyntaxFactory.CarriageReturnLineFeed;
var triviaList = SyntaxFactory.TriviaList(pragmaDirectiveTrivia);
var title = includeTitle ? diagnostic.Descriptor.Title.ToString(CultureInfo.CurrentUICulture) : null;
if (!string.IsNullOrWhiteSpace(title))
{
var titleComment = SyntaxFactory.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.IsKind(SyntaxKind.EndOfLineTrivia) || trivia.IsKind(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(SyntaxFactory.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(SyntaxFactory.Attribute(attributeName, attributeArguments));
AttributeListSyntax attributeList;
if (isAssemblyAttribute)
{
var targetSpecifier = SyntaxFactory.AttributeTargetSpecifier(SyntaxFactory.Token(SyntaxKind.AssemblyKeyword));
attributeList = SyntaxFactory.AttributeList(targetSpecifier, attributes);
}
else
{
attributeList = SyntaxFactory.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 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(diagnostic.Descriptor.Category));
var categoryArgument = SyntaxFactory.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 = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(ruleIdText));
var ruleIdArgument = SyntaxFactory.AttributeArgument(ruleId);
var justificationExpr = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(FeaturesResources.Pending));
var justificationArgument = SyntaxFactory.AttributeArgument(SyntaxFactory.NameEquals("Justification"), nameColon: null, expression: justificationExpr);
var attributeArgumentList = SyntaxFactory.AttributeArgumentList().AddArguments(categoryArgument, ruleIdArgument, justificationArgument);
if (isAssemblyAttribute)
{
var scopeString = GetScopeString(targetSymbol.Kind);
if (scopeString != null)
{
var scopeExpr = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(scopeString));
var scopeArgument = SyntaxFactory.AttributeArgument(SyntaxFactory.NameEquals("Scope"), nameColon: null, expression: scopeExpr);
var targetString = GetTargetString(targetSymbol);
var targetExpr = SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(targetString));
var targetArgument = SyntaxFactory.AttributeArgument(SyntaxFactory.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 = SyntaxFactory.Token(currentKeyword.LeadingTrivia, toggledKeywordKind, currentKeyword.TrailingTrivia);
var newPragmaWarning = pragmaWarning.WithDisableOrRestoreKeyword(toggledToken);
return SyntaxFactory.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;
}
}
|