Skip to content

Commit 28c3905

Browse files
committed
Implement DOC103 (Use Unicode Characters)
Closes #1
1 parent bfe856e commit 28c3905

File tree

8 files changed

+374
-0
lines changed

8 files changed

+374
-0
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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.Net;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using DocumentationAnalyzers.Helpers;
12+
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CodeActions;
14+
using Microsoft.CodeAnalysis.CodeFixes;
15+
using Microsoft.CodeAnalysis.CSharp;
16+
17+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC103CodeFixProvider))]
18+
[Shared]
19+
internal class DOC103CodeFixProvider : CodeFixProvider
20+
{
21+
private const string CS1570 = nameof(CS1570);
22+
23+
public override ImmutableArray<string> FixableDiagnosticIds { get; }
24+
= ImmutableArray.Create(DOC103UseUnicodeCharacters.DiagnosticId, CS1570);
25+
26+
public override FixAllProvider GetFixAllProvider()
27+
=> CustomFixAllProviders.BatchFixer;
28+
29+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
30+
{
31+
foreach (var diagnostic in context.Diagnostics)
32+
{
33+
if (!FixableDiagnosticIds.Contains(diagnostic.Id))
34+
{
35+
continue;
36+
}
37+
38+
context.RegisterCodeFix(
39+
CodeAction.Create(
40+
StyleResources.DOC103CodeFix,
41+
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
42+
nameof(DOC103CodeFixProvider)),
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+
SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);
53+
54+
string newText = token.ValueText;
55+
if (newText == token.Text)
56+
{
57+
// The entity is not recognized. Try decoding as an HTML entity.
58+
newText = WebUtility.HtmlDecode(token.Text);
59+
}
60+
61+
if (newText == token.Text)
62+
{
63+
// Unknown entity
64+
return document;
65+
}
66+
67+
var newToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.XmlTextLiteralToken, newText, newText, token.TrailingTrivia);
68+
69+
return document.WithSyntaxRoot(root.ReplaceToken(token, newToken));
70+
}
71+
}
72+
}
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: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
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+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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 Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
12+
/// <summary>
13+
/// Use Unicode characters.
14+
/// </summary>
15+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
16+
internal class DOC103UseUnicodeCharacters : DiagnosticAnalyzer
17+
{
18+
/// <summary>
19+
/// The ID for diagnostics produced by the <see cref="DOC103UseUnicodeCharacters"/> analyzer.
20+
/// </summary>
21+
public const string DiagnosticId = "DOC103";
22+
private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC103.md";
23+
24+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(StyleResources.DOC103Title), StyleResources.ResourceManager, typeof(StyleResources));
25+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(StyleResources.DOC103MessageFormat), StyleResources.ResourceManager, typeof(StyleResources));
26+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(StyleResources.DOC103Description), StyleResources.ResourceManager, typeof(StyleResources));
27+
28+
private static readonly DiagnosticDescriptor Descriptor =
29+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.PortabilityRules, DiagnosticSeverity.Info, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
30+
31+
/// <inheritdoc/>
32+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
33+
= ImmutableArray.Create(Descriptor);
34+
35+
public override void Initialize(AnalysisContext context)
36+
{
37+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
38+
context.EnableConcurrentExecution();
39+
40+
context.RegisterSyntaxNodeAction(HandleXmlElementSyntax, SyntaxKind.XmlText);
41+
}
42+
43+
private static void HandleXmlElementSyntax(SyntaxNodeAnalysisContext context)
44+
{
45+
var xmlText = (XmlTextSyntax)context.Node;
46+
foreach (var token in xmlText.TextTokens)
47+
{
48+
if (!token.IsKind(SyntaxKind.XmlEntityLiteralToken))
49+
{
50+
continue;
51+
}
52+
53+
switch (token.ValueText)
54+
{
55+
// Characters which are often XML-escaped unnecessarily
56+
case "'":
57+
case "\"":
58+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, token.GetLocation()));
59+
break;
60+
61+
default:
62+
continue;
63+
}
64+
}
65+
}
66+
}
67+
}

DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.Designer.cs

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.resx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,16 @@
147147
<data name="DOC102Title" xml:space="preserve">
148148
<value>Use child blocks consistently across elements of the same kind</value>
149149
</data>
150+
<data name="DOC103CodeFix" xml:space="preserve">
151+
<value>Use Unicode characters</value>
152+
</data>
153+
<data name="DOC103Description" xml:space="preserve">
154+
<value>Use Unicode characters</value>
155+
</data>
156+
<data name="DOC103MessageFormat" xml:space="preserve">
157+
<value>Use Unicode characters</value>
158+
</data>
159+
<data name="DOC103Title" xml:space="preserve">
160+
<value>Use Unicode characters</value>
161+
</data>
150162
</root>

docs/DOC103.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# DOC103
2+
3+
<table>
4+
<tr>
5+
<td>TypeName</td>
6+
<td>DOC103UseUnicodeCharacters</td>
7+
</tr>
8+
<tr>
9+
<td>CheckId</td>
10+
<td>DOC103</td>
11+
</tr>
12+
<tr>
13+
<td>Category</td>
14+
<td>Style Rules</td>
15+
</tr>
16+
</table>
17+
18+
## Cause
19+
20+
The documentation contains an unnecessary or unrecognized HTML character entity.
21+
22+
## Rule description
23+
24+
A violation of this rule occurs when documentation an unnecessarily-escaped character, making the documentation more
25+
difficult to read than necessary. The code fix for this diagnostic also helps correct HTML character entity references
26+
(e.g. `&rarr`), which are not supported by the XML documentation compiler.
27+
28+
```csharp
29+
/// <summary>
30+
/// A city&apos;s graph from A&rarr;B.
31+
/// </summary>
32+
public class SomeType
33+
{
34+
}
35+
```
36+
37+
## How to fix violations
38+
39+
To fix a violation of this rule, replace the XML- or HTML-escape sequence with the intended character.
40+
41+
```csharp
42+
/// <summary>
43+
/// A city's graph from A→B.
44+
/// </summary>
45+
public class SomeType
46+
{
47+
}
48+
```
49+
50+
## How to suppress violations
51+
52+
```csharp
53+
#pragma warning disable DOC103 // Use Unicode characters
54+
/// <summary>
55+
/// A city&apos;s graph from A&rarr;B.
56+
/// </summary>
57+
public class SomeType
58+
#pragma warning restore DOC103 // Use Unicode characters
59+
{
60+
}
61+
```

docs/StyleRules.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ Identifier | Name | Description
77
[DOC100](DOC100.md) | PlaceTextInParagraphs | A `<remarks>` or `<note>` documentation element contains content which is not wrapped in a block-level element.
88
[DOC101](DOC101.md) | UseChildBlocksConsistently | The documentation for the element contains some text which is wrapped in block-level elements, and other text which is written inline.
99
[DOC102](DOC102.md) | UseChildBlocksConsistentlyAcrossElementsOfTheSameKind | The documentation for the element contains inline text, but the documentation for a sibling element of the same kind uses block-level elements.
10+
[DOC103](DOC103.md) | UseUnicodeCharacters | The documentation contains an unnecessary or unrecognized HTML character entity.

0 commit comments

Comments
 (0)