// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.CodeFixes.Suppression { internal partial class AbstractSuppressionCodeFixProvider { /// /// Helper methods for pragma based suppression code actions. /// private static class PragmaHelpers { internal async static Task GetChangeDocumentWithPragmaAdjustedAsync( Document document, TextSpan diagnosticSpan, SuppressionTargetInfo suppressionTargetInfo, Func getNewStartToken, Func getNewEndToken, CancellationToken cancellationToken) { var startToken = suppressionTargetInfo.StartToken; var endToken = suppressionTargetInfo.EndToken; var nodeWithTokens = suppressionTargetInfo.NodeWithTokens; var root = await nodeWithTokens.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); var startAndEndTokenAreTheSame = startToken == endToken; SyntaxToken newStartToken = getNewStartToken(startToken, diagnosticSpan); SyntaxToken newEndToken = endToken; if (startAndEndTokenAreTheSame) { var annotation = new SyntaxAnnotation(); newEndToken = root.ReplaceToken(startToken, newStartToken.WithAdditionalAnnotations(annotation)).GetAnnotatedTokens(annotation).Single(); var spanChange = newStartToken.LeadingTrivia.FullSpan.Length - startToken.LeadingTrivia.FullSpan.Length; diagnosticSpan = new TextSpan(diagnosticSpan.Start + spanChange, diagnosticSpan.Length); } newEndToken = getNewEndToken(newEndToken, diagnosticSpan); SyntaxNode newNode; if (startAndEndTokenAreTheSame) { newNode = nodeWithTokens.ReplaceToken(startToken, newEndToken); } else { newNode = nodeWithTokens.ReplaceTokens(new[] { startToken, endToken }, (o, n) => o == startToken ? newStartToken : newEndToken); } var newRoot = root.ReplaceNode(nodeWithTokens, newNode); return document.WithSyntaxRoot(newRoot); } private static int GetPositionForPragmaInsertion(ImmutableArray triviaList, TextSpan currentDiagnosticSpan, AbstractSuppressionCodeFixProvider fixer, bool isStartToken, out SyntaxTrivia triviaAtIndex) { // Start token: Insert the #pragma disable directive just **before** the first end of line trivia prior to diagnostic location. // End token: Insert the #pragma disable directive just **after** the first end of line trivia after diagnostic location. Func getNextIndex = cur => isStartToken ? cur - 1 : cur + 1; Func shouldConsiderTrivia = trivia => isStartToken ? trivia.FullSpan.End <= currentDiagnosticSpan.Start : trivia.FullSpan.Start >= currentDiagnosticSpan.End; var walkedPastDiagnosticSpan = false; var seenEndOfLineTrivia = false; var index = isStartToken ? triviaList.Length - 1 : 0; while (index >= 0 && index < triviaList.Length) { var trivia = triviaList[index]; walkedPastDiagnosticSpan = walkedPastDiagnosticSpan || shouldConsiderTrivia(trivia); seenEndOfLineTrivia = seenEndOfLineTrivia || (fixer.IsEndOfLine(trivia) || (trivia.HasStructure && trivia.GetStructure().DescendantTrivia().Any(t => fixer.IsEndOfLine(t)))); if (walkedPastDiagnosticSpan && seenEndOfLineTrivia) { break; } index = getNextIndex(index); } triviaAtIndex = index >= 0 && index < triviaList.Length ? triviaList[index] : default(SyntaxTrivia); return index; } internal static SyntaxToken GetNewStartTokenWithAddedPragma(SyntaxToken startToken, TextSpan currentDiagnosticSpan, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer, Func formatNode, bool isRemoveSuppression = false) { var trivia = startToken.LeadingTrivia.ToImmutableArray(); SyntaxTrivia insertAfterTrivia; var index = GetPositionForPragmaInsertion(trivia, currentDiagnosticSpan, fixer, isStartToken: true, triviaAtIndex: out insertAfterTrivia); index++; bool needsLeadingEOL; if (index > 0) { needsLeadingEOL = !fixer.IsEndOfLine(insertAfterTrivia); } else if (startToken.FullSpan.Start == 0) { needsLeadingEOL = false; } else { needsLeadingEOL = true; } var pragmaTrivia = !isRemoveSuppression ? fixer.CreatePragmaDisableDirectiveTrivia(diagnostic, formatNode, needsLeadingEOL, needsTrailingEndOfLine: true) : fixer.CreatePragmaRestoreDirectiveTrivia(diagnostic, formatNode, needsLeadingEOL, needsTrailingEndOfLine: true); return startToken.WithLeadingTrivia(trivia.InsertRange(index, pragmaTrivia)); } internal static SyntaxToken GetNewEndTokenWithAddedPragma(SyntaxToken endToken, TextSpan currentDiagnosticSpan, Diagnostic diagnostic, AbstractSuppressionCodeFixProvider fixer, Func formatNode, bool isRemoveSuppression = false) { ImmutableArray trivia; var isEOF = fixer.IsEndOfFileToken(endToken); if (isEOF) { trivia = endToken.LeadingTrivia.ToImmutableArray(); } else { trivia = endToken.TrailingTrivia.ToImmutableArray(); } SyntaxTrivia insertBeforeTrivia; var index = GetPositionForPragmaInsertion(trivia, currentDiagnosticSpan, fixer, isStartToken: false, triviaAtIndex: out insertBeforeTrivia); bool needsTrailingEOL; if (index < trivia.Length) { needsTrailingEOL = !fixer.IsEndOfLine(insertBeforeTrivia); } else if (isEOF) { needsTrailingEOL = false; } else { needsTrailingEOL = true; } var pragmaTrivia = !isRemoveSuppression ? fixer.CreatePragmaRestoreDirectiveTrivia(diagnostic, formatNode, needsLeadingEndOfLine: true, needsTrailingEndOfLine: needsTrailingEOL) : fixer.CreatePragmaDisableDirectiveTrivia(diagnostic, formatNode, needsLeadingEndOfLine: true, needsTrailingEndOfLine: needsTrailingEOL); if (isEOF) { return endToken.WithLeadingTrivia(trivia.InsertRange(index, pragmaTrivia)); } else { return endToken.WithTrailingTrivia(trivia.InsertRange(index, pragmaTrivia)); }; } } } }