internal static async Task <SyntaxNode> GetTransformedSyntaxRootAsync( ISyntaxFacts syntaxFacts, AbstractFileHeaderHelper fileHeaderHelper, SyntaxTrivia newLineTrivia, Document document, CancellationToken cancellationToken ) { var tree = await document .GetRequiredSyntaxTreeAsync(cancellationToken) .ConfigureAwait(false); var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); if ( !document.Project.AnalyzerOptions.TryGetEditorConfigOption <string>( CodeStyleOptions2.FileHeaderTemplate, tree, out var fileHeaderTemplate ) || string.IsNullOrEmpty(fileHeaderTemplate) ) { // This exception would show up as a gold bar, but as indicated we do not believe this is reachable. throw ExceptionUtilities.Unreachable; } var expectedFileHeader = fileHeaderTemplate.Replace( "{fileName}", Path.GetFileName(document.FilePath) ); var fileHeader = fileHeaderHelper.ParseFileHeader(root); SyntaxNode newSyntaxRoot; if (fileHeader.IsMissing) { newSyntaxRoot = AddHeader( syntaxFacts, fileHeaderHelper, newLineTrivia, root, expectedFileHeader ); } else { newSyntaxRoot = ReplaceHeader( syntaxFacts, fileHeaderHelper, newLineTrivia, root, expectedFileHeader ); } return(newSyntaxRoot); }
private static SyntaxNode AddHeader( ISyntaxFacts syntaxFacts, AbstractFileHeaderHelper fileHeaderHelper, SyntaxTrivia newLineTrivia, SyntaxNode root, string expectedFileHeader ) { var newTrivia = CreateNewHeader( syntaxFacts, fileHeaderHelper.CommentPrefix, expectedFileHeader, newLineTrivia.ToFullString() ) .Add(newLineTrivia) .Add(newLineTrivia); // Skip blank lines already at the beginning of the document, since we add our own var leadingTrivia = root.GetLeadingTrivia(); var skipCount = 0; for (var i = 0; i < leadingTrivia.Count; i++) { if (leadingTrivia[i].RawKind == syntaxFacts.SyntaxKinds.EndOfLineTrivia) { skipCount = i + 1; } else if (leadingTrivia[i].RawKind != syntaxFacts.SyntaxKinds.WhitespaceTrivia) { break; } } newTrivia = newTrivia.AddRange(leadingTrivia.Skip(skipCount)); return(root.WithLeadingTrivia(newTrivia)); }
internal static async Task <SyntaxNode> GetTransformedSyntaxRootAsync(ISyntaxFacts syntaxFacts, AbstractFileHeaderHelper fileHeaderHelper, SyntaxTrivia newLineTrivia, Document document, string?fileHeaderTemplate, CancellationToken cancellationToken) { var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var root = await tree.GetRootAsync(cancellationToken).ConfigureAwait(false); // If we weren't given a header lets get the one from editorconfig if (fileHeaderTemplate is null && !document.Project.AnalyzerOptions.AnalyzerConfigOptionsProvider.GetOptions(tree).TryGetEditorConfigOption(CodeStyleOptions2.FileHeaderTemplate, out fileHeaderTemplate)) { // No header supplied, no editorconfig setting, nothing to do return(root); } if (RoslynString.IsNullOrEmpty(fileHeaderTemplate)) { // Header template is empty, nothing to do. This shouldn't be possible if this method is called in // reaction to a diagnostic, but this method is also used when creating new documents so lets be defensive. return(root); } var expectedFileHeader = fileHeaderTemplate.Replace("{fileName}", Path.GetFileName(document.FilePath)); var fileHeader = fileHeaderHelper.ParseFileHeader(root); SyntaxNode newSyntaxRoot; if (fileHeader.IsMissing) { newSyntaxRoot = AddHeader(syntaxFacts, fileHeaderHelper, newLineTrivia, root, expectedFileHeader); } else { newSyntaxRoot = ReplaceHeader(syntaxFacts, fileHeaderHelper, newLineTrivia, root, expectedFileHeader); } return(newSyntaxRoot); }
private static SyntaxNode ReplaceHeader(ISyntaxFacts syntaxFacts, AbstractFileHeaderHelper fileHeaderHelper, SyntaxTrivia newLineTrivia, SyntaxNode root, string expectedFileHeader) { // Skip single line comments, whitespace, and end of line trivia until a blank line is encountered. var triviaList = root.GetLeadingTrivia(); // True if the current line is blank so far (empty or whitespace); otherwise, false. The first line is // assumed to not be blank, which allows the analysis to detect a file header which follows a blank line at // the top of the file. var onBlankLine = false; // The set of indexes to remove from 'triviaList'. After removing these indexes, the remaining trivia (if // any) will be preserved in the document along with the replacement header. var removalList = new List <int>(); // The number of spaces to indent the new header. This is expected to match the indentation of the header // which is being replaced. var leadingSpaces = string.Empty; // The number of spaces found so far on the current line. This will become 'leadingSpaces' if the spaces are // followed by a comment which is considered a header comment. var possibleLeadingSpaces = string.Empty; // Need to do this with index so we get the line endings correct. for (var i = 0; i < triviaList.Count; i++) { var triviaLine = triviaList[i]; if (triviaLine.RawKind == syntaxFacts.SyntaxKinds.SingleLineCommentTrivia) { if (possibleLeadingSpaces != string.Empty) { // One or more spaces precedes the comment. Keep track of these spaces so we can indent the new // header by the same amount. leadingSpaces = possibleLeadingSpaces; } removalList.Add(i); onBlankLine = false; } else if (triviaLine.RawKind == syntaxFacts.SyntaxKinds.WhitespaceTrivia) { if (leadingSpaces == string.Empty) { possibleLeadingSpaces = triviaLine.ToFullString(); } removalList.Add(i); } else if (triviaLine.RawKind == syntaxFacts.SyntaxKinds.EndOfLineTrivia) { possibleLeadingSpaces = string.Empty; removalList.Add(i); if (onBlankLine) { break; } else { onBlankLine = true; } } else { break; } } // Remove copyright lines in reverse order. for (var i = removalList.Count - 1; i >= 0; i--) { triviaList = triviaList.RemoveAt(removalList[i]); } var newHeaderTrivia = CreateNewHeader(syntaxFacts, leadingSpaces + fileHeaderHelper.CommentPrefix, expectedFileHeader, newLineTrivia.ToFullString()); // Add a blank line and any remaining preserved trivia after the header. newHeaderTrivia = newHeaderTrivia.Add(newLineTrivia).Add(newLineTrivia).AddRange(triviaList); // Insert header at top of the file. return(root.WithLeadingTrivia(newHeaderTrivia)); }