Skip to content

Commit b0a3f2a

Browse files
committed
Implement DOC901 (Convert to Documentation Comment)
Closes #52
1 parent 621b78b commit b0a3f2a

File tree

8 files changed

+714
-0
lines changed

8 files changed

+714
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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+
{
31+
// this is unlikely to work as expected
32+
return null;
33+
}
34+
35+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
36+
{
37+
foreach (var diagnostic in context.Diagnostics)
38+
{
39+
Debug.Assert(FixableDiagnosticIds.Contains(diagnostic.Id), "Assertion failed: FixableDiagnosticIds.Contains(diagnostic.Id)");
40+
41+
context.RegisterCodeFix(
42+
CodeAction.Create(
43+
RefactoringResources.DOC901CodeFix,
44+
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
45+
nameof(DOC901CodeFixProvider)),
46+
diagnostic);
47+
}
48+
49+
return SpecializedTasks.CompletedTask;
50+
}
51+
52+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
53+
{
54+
SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
55+
56+
var firstComment = root.FindTrivia(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);
57+
var parentToken = firstComment.Token;
58+
59+
var lines = new List<string>();
60+
for (int i = 0; i < parentToken.LeadingTrivia.Count; i++)
61+
{
62+
if (!parentToken.LeadingTrivia[i].IsKind(SyntaxKind.SingleLineCommentTrivia)
63+
&& !parentToken.LeadingTrivia[i].IsKind(SyntaxKind.MultiLineCommentTrivia))
64+
{
65+
continue;
66+
}
67+
68+
if (!diagnostic.Location.SourceSpan.Contains(parentToken.LeadingTrivia[i].Span))
69+
{
70+
continue;
71+
}
72+
73+
if (parentToken.LeadingTrivia[i].IsKind(SyntaxKind.SingleLineCommentTrivia))
74+
{
75+
lines.Add(parentToken.LeadingTrivia[i].ToString().Substring(2));
76+
}
77+
else
78+
{
79+
var commentText = parentToken.LeadingTrivia[i].ToString();
80+
var normalizedText = commentText.Substring(1, commentText.Length - 3)
81+
.Replace("\r\n", "\n").Replace('\r', '\n');
82+
foreach (var line in normalizedText.Split('\n'))
83+
{
84+
if (Regex.IsMatch(line, "^\\s*\\*"))
85+
{
86+
lines.Add(line.Substring(line.IndexOf('*') + 1));
87+
}
88+
else
89+
{
90+
lines.Add(line);
91+
}
92+
}
93+
94+
lines[lines.Count - 1] = lines[lines.Count - 1].TrimEnd();
95+
}
96+
}
97+
98+
int firstContentLine = lines.FindIndex(line => !string.IsNullOrWhiteSpace(line));
99+
if (firstContentLine >= 0)
100+
{
101+
lines.RemoveRange(0, firstContentLine);
102+
int lastContentLine = lines.FindLastIndex(line => !string.IsNullOrWhiteSpace(line));
103+
lines.RemoveRange(lastContentLine + 1, lines.Count - lastContentLine - 1);
104+
}
105+
106+
if (lines.All(line => line.Length == 0 || line.StartsWith(" ")))
107+
{
108+
for (int i = 0; i < lines.Count; i++)
109+
{
110+
if (lines[i].Length == 0)
111+
{
112+
continue;
113+
}
114+
115+
lines[i] = lines[i].Substring(1);
116+
}
117+
}
118+
119+
var nodes = new List<XmlNodeSyntax>(lines.Select(line => XmlSyntaxFactory.Text(line)));
120+
for (int i = nodes.Count - 1; i > 0; i--)
121+
{
122+
nodes.Insert(i, XmlSyntaxFactory.NewLine(Environment.NewLine));
123+
}
124+
125+
var summary = XmlSyntaxFactory.SummaryElement(Environment.NewLine, nodes.ToArray());
126+
127+
var leadingTrivia = SyntaxFactory.TriviaList(parentToken.LeadingTrivia.TakeWhile(trivia => !trivia.Equals(firstComment)));
128+
var newParentToken = parentToken.WithLeadingTrivia(leadingTrivia.Add(SyntaxFactory.Trivia(XmlSyntaxFactory.DocumentationComment(Environment.NewLine, summary))));
129+
130+
var newRoot = root.ReplaceToken(parentToken, newParentToken);
131+
return document.WithSyntaxRoot(root.ReplaceToken(parentToken, newParentToken));
132+
}
133+
}
134+
}
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)