Skip to content

Commit

Permalink
Merge pull request #57 from sharwell/use-unicode
Browse files Browse the repository at this point in the history
Implement DOC103 (Use Unicode Characters)
  • Loading branch information
sharwell authored Sep 21, 2018
2 parents d4ca1c6 + d420b0a commit d33668c
Show file tree
Hide file tree
Showing 8 changed files with 508 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT license. See LICENSE in the project root for license information.

namespace DocumentationAnalyzers.StyleRules
{
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using DocumentationAnalyzers.Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC103CodeFixProvider))]
[Shared]
internal class DOC103CodeFixProvider : CodeFixProvider
{
private const string CS1570 = nameof(CS1570);

public override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DOC103UseUnicodeCharacters.DiagnosticId, CS1570);

public override FixAllProvider GetFixAllProvider()
=> CustomFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

foreach (var diagnostic in context.Diagnostics)
{
Debug.Assert(FixableDiagnosticIds.Contains(diagnostic.Id), "Assertion failed: FixableDiagnosticIds.Contains(diagnostic.Id)");

SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);
if (!token.IsKind(SyntaxKind.XmlEntityLiteralToken))
{
// Could be an unrelated CS1570 error.
return;
}

string newText = token.ValueText;
if (newText == token.Text)
{
// The entity is not recognized. Try decoding as an HTML entity.
newText = WebUtility.HtmlDecode(token.Text);
}

if (newText == token.Text)
{
// Unknown entity
continue;
}

context.RegisterCodeFix(
CodeAction.Create(
StyleResources.DOC103CodeFix,
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, newText, cancellationToken),
nameof(DOC103CodeFixProvider)),
diagnostic);
}
}

private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, string newText, CancellationToken cancellationToken)
{
SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);

var newToken = SyntaxFactory.Token(token.LeadingTrivia, SyntaxKind.XmlTextLiteralToken, newText, newText, token.TrailingTrivia);

return document.WithSyntaxRoot(root.ReplaceToken(token, newToken));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT license. See LICENSE in the project root for license information.

namespace DocumentationAnalyzers.Test.CSharp7.StyleRules
{
using DocumentationAnalyzers.Test.StyleRules;

public class DOC103CSharp7UnitTests : DOC103UnitTests
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT license. See LICENSE in the project root for license information.

namespace DocumentationAnalyzers.Test.StyleRules
{
using System.Threading.Tasks;
using DocumentationAnalyzers.StyleRules;
using Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Microsoft.CodeAnalysis.Testing.Verifiers;
using Xunit;
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.StyleRules.DOC103UseUnicodeCharacters, DocumentationAnalyzers.StyleRules.DOC103CodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;

public class DOC103UnitTests
{
[Fact]
public async Task TestApostropheReplacementAsync()
{
var testCode = @"
/// <summary>
/// Don[|&apos;|]t use <![CDATA[&apos;]]> this <element attr=""&apos;"" attr2='&apos;'/>.
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// Don't use <![CDATA[&apos;]]> this <element attr=""&apos;"" attr2='&apos;'/>.
/// </summary>
class TestClass
{
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestApostropheReplacementByNumberAsync()
{
var testCode = @"
/// <summary>
/// Don[|&#39;|]t use <![CDATA[&#39;]]> this <element attr=""&#39;"" attr2='&#39;'/>.
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// Don't use <![CDATA[&#39;]]> this <element attr=""&#39;"" attr2='&#39;'/>.
/// </summary>
class TestClass
{
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestQuoteReplacementAsync()
{
var testCode = @"
/// <summary>
/// Don[|&quot;|]t use <![CDATA[&quot;]]> this <element attr=""&quot;"" attr2='&quot;'/>.
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// Don""t use <![CDATA[&quot;]]> this <element attr=""&quot;"" attr2='&quot;'/>.
/// </summary>
class TestClass
{
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestHtmlEntityReplacementAsync()
{
var testCode = @"
/// <summary>
/// From A&rarr;B.
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// From A→B.
/// </summary>
class TestClass
{
}
";

await new CSharpCodeFixTest<DOC103UseUnicodeCharacters, DOC103CodeFixProvider, XUnitVerifier>
{
TestCode = testCode,
ExpectedDiagnostics = { DiagnosticResult.CompilerWarning("CS1570").WithSpan(3, 11, 3, 11).WithMessage("XML comment has badly formed XML -- 'Reference to undefined entity 'rarr'.'") },
FixedCode = fixedCode,
CompilerDiagnostics = CompilerDiagnostics.Warnings,
}.RunAsync();
}

[Fact]
public async Task TestUnknownEntityNotReplacedAsync()
{
var testCode = @"
/// <summary>
/// Unknown entity &myEntity;.
/// </summary>
class TestClass
{
}
";
var fixedCode = testCode;

await new CSharpCodeFixTest<DOC103UseUnicodeCharacters, DOC103CodeFixProvider, XUnitVerifier>
{
TestState =
{
Sources = { testCode },
ExpectedDiagnostics = { DiagnosticResult.CompilerWarning("CS1570").WithSpan(3, 20, 3, 20).WithMessage("XML comment has badly formed XML -- 'Reference to undefined entity 'myEntity'.'") },
},
FixedState =
{
Sources = { fixedCode },
InheritanceMode = StateInheritanceMode.AutoInheritAll,
},
CompilerDiagnostics = CompilerDiagnostics.Warnings,
}.RunAsync();
}

[Fact]
public async Task TestHtmlEntityReplacementInInvalidXmlAsync()
{
var testCode = @"
/// <summary>
/// From A&rarr;B.
/// <p>
/// An unterminated second paragraph...
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// From A→B.
/// <p>
/// An unterminated second paragraph...
/// </summary>
class TestClass
{
}
";

await new CSharpCodeFixTest<DOC103UseUnicodeCharacters, DOC103CodeFixProvider, XUnitVerifier>
{
TestState =
{
Sources = { testCode },
ExpectedDiagnostics =
{
DiagnosticResult.CompilerWarning("CS1570").WithSpan(3, 11, 3, 11).WithMessage("XML comment has badly formed XML -- 'Reference to undefined entity 'rarr'.'"),
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'.'"),
DiagnosticResult.CompilerWarning("CS1570").WithSpan(7, 1, 7, 1).WithMessage("XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'"),
},
},
FixedState =
{
Sources = { fixedCode },
ExpectedDiagnostics =
{
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'.'"),
DiagnosticResult.CompilerWarning("CS1570").WithSpan(7, 1, 7, 1).WithMessage("XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'"),
},
},
CompilerDiagnostics = CompilerDiagnostics.Warnings,
}.RunAsync();
}

[Fact]
public async Task TestNoCodeFixForRequiredEntityAsync()
{
var testCode = @"
/// <summary>
/// Processing for <c>&lt;code&gt;</c> elements.
/// </summary>
class TestClass
{
}
";
var fixedCode = testCode;

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestNoCodeFixForInvalidXmlAsync()
{
var testCode = @"
/// <summary>
/// From A to B.
/// <p>
/// An unterminated second paragraph...
/// </summary>
class TestClass
{
}
";
var fixedCode = testCode;

await new CSharpCodeFixTest<DOC103UseUnicodeCharacters, DOC103CodeFixProvider, XUnitVerifier>
{
TestState =
{
Sources = { testCode },
ExpectedDiagnostics =
{
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'.'"),
DiagnosticResult.CompilerWarning("CS1570").WithSpan(7, 1, 7, 1).WithMessage("XML comment has badly formed XML -- 'Expected an end tag for element 'summary'.'"),
},
},
FixedState =
{
Sources = { fixedCode },
InheritanceMode = StateInheritanceMode.AutoInheritAll,
},
CompilerDiagnostics = CompilerDiagnostics.Warnings,
}.RunAsync();
}
}
}
Loading

0 comments on commit d33668c

Please sign in to comment.