Skip to content

Commit 4a22934

Browse files
authored
Merge pull request #62 from sharwell/line-to-doc
Implement DOC901 (Convert to Documentation Comment)
2 parents 378ccf9 + a49410b commit 4a22934

File tree

8 files changed

+705
-0
lines changed

8 files changed

+705
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the MIT license. See LICENSE in the project root for license information.
3+
4+
namespace DocumentationAnalyzers.RefactoringRules
5+
{
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Collections.Immutable;
9+
using System.Composition;
10+
using System.Diagnostics;
11+
using System.Linq;
12+
using System.Text.RegularExpressions;
13+
using System.Threading;
14+
using System.Threading.Tasks;
15+
using DocumentationAnalyzers.Helpers;
16+
using Microsoft.CodeAnalysis;
17+
using Microsoft.CodeAnalysis.CodeActions;
18+
using Microsoft.CodeAnalysis.CodeFixes;
19+
using Microsoft.CodeAnalysis.CSharp;
20+
using Microsoft.CodeAnalysis.CSharp.Syntax;
21+
22+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC901CodeFixProvider))]
23+
[Shared]
24+
internal partial class DOC901CodeFixProvider : CodeFixProvider
25+
{
26+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
27+
ImmutableArray.Create(DOC901ConvertToDocumentationComment.DiagnosticId);
28+
29+
public override FixAllProvider GetFixAllProvider()
30+
=> CustomFixAllProviders.BatchFixer;
31+
32+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
33+
{
34+
foreach (var diagnostic in context.Diagnostics)
35+
{
36+
Debug.Assert(FixableDiagnosticIds.Contains(diagnostic.Id), "Assertion failed: FixableDiagnosticIds.Contains(diagnostic.Id)");
37+
38+
context.RegisterCodeFix(
39+
CodeAction.Create(
40+
RefactoringResources.DOC901CodeFix,
41+
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
42+
nameof(DOC901CodeFixProvider)),
43+
diagnostic);
44+
}
45+
46+
return SpecializedTasks.CompletedTask;
47+
}
48+
49+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
50+
{
51+
SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
52+
53+
var firstComment = root.FindTrivia(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);
54+
var parentToken = firstComment.Token;
55+
56+
var lines = new List<string>();
57+
for (int i = 0; i < parentToken.LeadingTrivia.Count; i++)
58+
{
59+
if (!parentToken.LeadingTrivia[i].IsKind(SyntaxKind.SingleLineCommentTrivia)
60+
&& !parentToken.LeadingTrivia[i].IsKind(SyntaxKind.MultiLineCommentTrivia))
61+
{
62+
continue;
63+
}
64+
65+
if (!diagnostic.Location.SourceSpan.Contains(parentToken.LeadingTrivia[i].Span))
66+
{
67+
continue;
68+
}
69+
70+
if (parentToken.LeadingTrivia[i].IsKind(SyntaxKind.SingleLineCommentTrivia))
71+
{
72+
lines.Add(parentToken.LeadingTrivia[i].ToString().Substring(2));
73+
}
74+
else
75+
{
76+
var commentText = parentToken.LeadingTrivia[i].ToString();
77+
var normalizedText = commentText.Substring(1, commentText.Length - 3)
78+
.Replace("\r\n", "\n").Replace('\r', '\n');
79+
foreach (var line in normalizedText.Split('\n'))
80+
{
81+
if (Regex.IsMatch(line, "^\\s*\\*"))
82+
{
83+
lines.Add(line.Substring(line.IndexOf('*') + 1));
84+
}
85+
else
86+
{
87+
lines.Add(line);
88+
}
89+
}
90+
91+
lines[lines.Count - 1] = lines[lines.Count - 1].TrimEnd();
92+
}
93+
}
94+
95+
int firstContentLine = lines.FindIndex(line => !string.IsNullOrWhiteSpace(line));
96+
if (firstContentLine >= 0)
97+
{
98+
lines.RemoveRange(0, firstContentLine);
99+
int lastContentLine = lines.FindLastIndex(line => !string.IsNullOrWhiteSpace(line));
100+
lines.RemoveRange(lastContentLine + 1, lines.Count - lastContentLine - 1);
101+
}
102+
103+
if (lines.All(line => line.Length == 0 || line.StartsWith(" ")))
104+
{
105+
for (int i = 0; i < lines.Count; i++)
106+
{
107+
if (lines[i].Length == 0)
108+
{
109+
continue;
110+
}
111+
112+
lines[i] = lines[i].Substring(1);
113+
}
114+
}
115+
116+
var nodes = new List<XmlNodeSyntax>(lines.Select(line => XmlSyntaxFactory.Text(line)));
117+
for (int i = nodes.Count - 1; i > 0; i--)
118+
{
119+
nodes.Insert(i, XmlSyntaxFactory.NewLine(Environment.NewLine));
120+
}
121+
122+
var summary = XmlSyntaxFactory.SummaryElement(Environment.NewLine, nodes.ToArray());
123+
124+
var leadingTrivia = SyntaxFactory.TriviaList(parentToken.LeadingTrivia.TakeWhile(trivia => !trivia.Equals(firstComment)));
125+
var newParentToken = parentToken.WithLeadingTrivia(leadingTrivia.Add(SyntaxFactory.Trivia(XmlSyntaxFactory.DocumentationComment(Environment.NewLine, summary))));
126+
127+
var newRoot = root.ReplaceToken(parentToken, newParentToken);
128+
return document.WithSyntaxRoot(root.ReplaceToken(parentToken, newParentToken));
129+
}
130+
}
131+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the MIT license. See LICENSE in the project root for license information.
3+
4+
namespace DocumentationAnalyzers.Test.CSharp7.RefactoringRules
5+
{
6+
using DocumentationAnalyzers.Test.RefactoringRules;
7+
8+
public class DOC901CSharp7UnitTests : DOC901UnitTests
9+
{
10+
}
11+
}

0 commit comments

Comments
 (0)