Skip to content

Commit d33668c

Browse files
authored
Merge pull request #57 from sharwell/use-unicode
Implement DOC103 (Use Unicode Characters)
2 parents d4ca1c6 + d420b0a commit d33668c

File tree

8 files changed

+508
-0
lines changed

8 files changed

+508
-0
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.StyleRules
5+
{
6+
using System.Collections.Immutable;
7+
using System.Composition;
8+
using System.Diagnostics;
9+
using System.Net;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using DocumentationAnalyzers.Helpers;
13+
using Microsoft.CodeAnalysis;
14+
using Microsoft.CodeAnalysis.CodeActions;
15+
using Microsoft.CodeAnalysis.CodeFixes;
16+
using Microsoft.CodeAnalysis.CSharp;
17+
18+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC103CodeFixProvider))]
19+
[Shared]
20+
internal class DOC103CodeFixProvider : CodeFixProvider
21+
{
22+
private const string CS1570 = nameof(CS1570);
23+
24+
public override ImmutableArray<string> FixableDiagnosticIds { get; }
25+
= ImmutableArray.Create(DOC103UseUnicodeCharacters.DiagnosticId, CS1570);
26+
27+
public override FixAllProvider GetFixAllProvider()
28+
=> CustomFixAllProviders.BatchFixer;
29+
30+
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
31+
{
32+
SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
33+
34+
foreach (var diagnostic in context.Diagnostics)
35+
{
36+
Debug.Assert(FixableDiagnosticIds.Contains(diagnostic.Id), "Assertion failed: FixableDiagnosticIds.Contains(diagnostic.Id)");
37+
38+
SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);
39+
if (!token.IsKind(SyntaxKind.XmlEntityLiteralToken))
40+
{
41+
// Could be an unrelated CS1570 error.
42+
return;
43+
}
44+
45+
string newText = token.ValueText;
46+
if (newText == token.Text)
47+
{
48+
// The entity is not recognized. Try decoding as an HTML entity.
49+
newText = WebUtility.HtmlDecode(token.Text);
50+
}
51+
52+
if (newText == token.Text)
53+
{
54+
// Unknown entity
55+
continue;
56+
}
57+
58+
context.RegisterCodeFix(
59+
CodeAction.Create(
60+
StyleResources.DOC103CodeFix,
61+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, newText, cancellationToken),
62+
nameof(DOC103CodeFixProvider)),
63+
diagnostic);
64+
}
65+
}
66+
67+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, string newText, CancellationToken cancellationToken)
68+
{
69+
SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
70+
SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);
71+
72+
var newToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.XmlTextLiteralToken, newText, newText, token.TrailingTrivia);
73+
74+
return document.WithSyntaxRoot(root.ReplaceToken(token, newToken));
75+
}
76+
}
77+
}
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.StyleRules
5+
{
6+
using DocumentationAnalyzers.Test.StyleRules;
7+
8+
public class DOC103CSharp7UnitTests : DOC103UnitTests
9+
{
10+
}
11+
}
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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.StyleRules
5+
{
6+
using System.Threading.Tasks;
7+
using DocumentationAnalyzers.StyleRules;
8+
using Microsoft.CodeAnalysis.CSharp.Testing;
9+
using Microsoft.CodeAnalysis.Testing;
10+
using Microsoft.CodeAnalysis.Testing.Verifiers;
11+
using Xunit;
12+
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.StyleRules.DOC103UseUnicodeCharacters, DocumentationAnalyzers.StyleRules.DOC103CodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;
13+
14+
public class DOC103UnitTests
15+
{
16+
[Fact]
17+
public async Task TestApostropheReplacementAsync()
18+
{
19+
var testCode = @"
20+
/// <summary>
21+
/// Don[|&apos;|]t use <![CDATA[&apos;]]> this <element attr=""&apos;"" attr2='&apos;'/>.
22+
/// </summary>
23+
class TestClass
24+
{
25+
}
26+
";
27+
var fixedCode = @"
28+
/// <summary>
29+
/// Don't use <![CDATA[&apos;]]> this <element attr=""&apos;"" attr2='&apos;'/>.
30+
/// </summary>
31+
class TestClass
32+
{
33+
}
34+
";
35+
36+
await Verify.VerifyCodeFixAsync(testCode, fixedCode);
37+
}
38+
39+
[Fact]
40+
public async Task TestApostropheReplacementByNumberAsync()
41+
{
42+
var testCode = @"
43+
/// <summary>
44+
/// Don[|&#39;|]t use <![CDATA[&#39;]]> this <element attr=""&#39;"" attr2='&#39;'/>.
45+
/// </summary>
46+
class TestClass
47+
{
48+
}
49+
";
50+
var fixedCode = @"
51+
/// <summary>
52+
/// Don't use <![CDATA[&#39;]]> this <element attr=""&#39;"" attr2='&#39;'/>.
53+
/// </summary>
54+
class TestClass
55+
{
56+
}
57+
";
58+
59+
await Verify.VerifyCodeFixAsync(testCode, fixedCode);
60+
}
61+
62+
[Fact]
63+
public async Task TestQuoteReplacementAsync()
64+
{
65+
var testCode = @"
66+
/// <summary>
67+
/// Don[|&quot;|]t use <![CDATA[&quot;]]> this <element attr=""&quot;"" attr2='&quot;'/>.
68+
/// </summary>
69+
class TestClass
70+
{
71+
}
72+
";
73+
var fixedCode = @"
74+
/// <summary>
75+
/// Don""t use <![CDATA[&quot;]]> this <element attr=""&quot;"" attr2='&quot;'/>.
76+
/// </summary>
77+
class TestClass
78+
{
79+
}
80+
";
81+
82+
await Verify.VerifyCodeFixAsync(testCode, fixedCode);
83+
}
84+
85+
[Fact]
86+
public async Task TestHtmlEntityReplacementAsync()
87+
{
88+
var testCode = @"
89+
/// <summary>
90+
/// From A&rarr;B.
91+
/// </summary>
92+
class TestClass
93+
{
94+
}
95+
";
96+
var fixedCode = @"
97+
/// <summary>
98+
/// From A→B.
99+
/// </summary>
100+
class TestClass
101+
{
102+
}
103+
";
104+
105+
await new CSharpCodeFixTest<DOC103UseUnicodeCharacters, DOC103CodeFixProvider, XUnitVerifier>
106+
{
107+
TestCode = testCode,
108+
ExpectedDiagnostics = { DiagnosticResult.CompilerWarning("CS1570").WithSpan(3, 11, 3, 11).WithMessage("XML comment has badly formed XML -- 'Reference to undefined entity 'rarr'.'") },
109+
FixedCode = fixedCode,
110+
CompilerDiagnostics = CompilerDiagnostics.Warnings,
111+
}.RunAsync();
112+
}
113+
114+
[Fact]
115+
public async Task TestUnknownEntityNotReplacedAsync()
116+
{
117+
var testCode = @"
118+
/// <summary>
119+
/// Unknown entity &myEntity;.
120+
/// </summary>
121+
class TestClass
122+
{
123+
}
124+
";
125+
var fixedCode = testCode;
126+
127+
await new CSharpCodeFixTest<DOC103UseUnicodeCharacters, DOC103CodeFixProvider, XUnitVerifier>
128+
{
129+
TestState =
130+
{
131+
Sources = { testCode },
132+
ExpectedDiagnostics = { DiagnosticResult.CompilerWarning("CS1570").WithSpan(3, 20, 3, 20).WithMessage("XML comment has badly formed XML -- 'Reference to undefined entity 'myEntity'.'") },
133+
},
134+
FixedState =
135+
{
136+
Sources = { fixedCode },
137+
InheritanceMode = StateInheritanceMode.AutoInheritAll,
138+
},
139+
CompilerDiagnostics = CompilerDiagnostics.Warnings,
140+
}.RunAsync();
141+
}
142+
143+
[Fact]
144+
public async Task TestHtmlEntityReplacementInInvalidXmlAsync()
145+
{
146+
var testCode = @"
147+
/// <summary>
148+
/// From A&rarr;B.
149+
/// <p>
150+
/// An unterminated second paragraph...
151+
/// </summary>
152+
class TestClass
153+
{
154+
}
155+
";
156+
var fixedCode = @"
157+
/// <summary>
158+
/// From A→B.
159+
/// <p>
160+
/// An unterminated second paragraph...
161+
/// </summary>
162+
class TestClass
163+
{
164+
}
165+
";
166+
167+
await new CSharpCodeFixTest<DOC103UseUnicodeCharacters, DOC103CodeFixProvider, XUnitVerifier>
168+
{
169+
TestState =
170+
{
171+
Sources = { testCode },
172+
ExpectedDiagnostics =
173+
{
174+
DiagnosticResult.CompilerWarning("CS1570").WithSpan(3, 11, 3, 11).WithMessage("XML comment has badly formed XML -- 'Reference to undefined entity 'rarr'.'"),
175+
DiagnosticResult.CompilerWarning("CS1570").WithSpan(6, 7, 6, 14).WithMessage("XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'p'.'"),
176+
DiagnosticResult.CompilerWarning("CS1570").WithSpan(7, 1, 7, 1).WithMessage("XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'"),
177+
},
178+
},
179+
FixedState =
180+
{
181+
Sources = { fixedCode },
182+
ExpectedDiagnostics =
183+
{
184+
DiagnosticResult.CompilerWarning("CS1570").WithSpan(6, 7, 6, 14).WithMessage("XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'p'.'"),
185+
DiagnosticResult.CompilerWarning("CS1570").WithSpan(7, 1, 7, 1).WithMessage("XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'"),
186+
},
187+
},
188+
CompilerDiagnostics = CompilerDiagnostics.Warnings,
189+
}.RunAsync();
190+
}
191+
192+
[Fact]
193+
public async Task TestNoCodeFixForRequiredEntityAsync()
194+
{
195+
var testCode = @"
196+
/// <summary>
197+
/// Processing for <c>&lt;code&gt;</c> elements.
198+
/// </summary>
199+
class TestClass
200+
{
201+
}
202+
";
203+
var fixedCode = testCode;
204+
205+
await Verify.VerifyCodeFixAsync(testCode, fixedCode);
206+
}
207+
208+
[Fact]
209+
public async Task TestNoCodeFixForInvalidXmlAsync()
210+
{
211+
var testCode = @"
212+
/// <summary>
213+
/// From A to B.
214+
/// <p>
215+
/// An unterminated second paragraph...
216+
/// </summary>
217+
class TestClass
218+
{
219+
}
220+
";
221+
var fixedCode = testCode;
222+
223+
await new CSharpCodeFixTest<DOC103UseUnicodeCharacters, DOC103CodeFixProvider, XUnitVerifier>
224+
{
225+
TestState =
226+
{
227+
Sources = { testCode },
228+
ExpectedDiagnostics =
229+
{
230+
DiagnosticResult.CompilerWarning("CS1570").WithSpan(6, 7, 6, 14).WithMessage("XML comment has badly formed XML -- 'End tag 'summary' does not match the start tag 'p'.'"),
231+
DiagnosticResult.CompilerWarning("CS1570").WithSpan(7, 1, 7, 1).WithMessage("XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'"),
232+
},
233+
},
234+
FixedState =
235+
{
236+
Sources = { fixedCode },
237+
InheritanceMode = StateInheritanceMode.AutoInheritAll,
238+
},
239+
CompilerDiagnostics = CompilerDiagnostics.Warnings,
240+
}.RunAsync();
241+
}
242+
}
243+
}

0 commit comments

Comments
 (0)