Skip to content

Commit

Permalink
Merge pull request #68 from sharwell/verify-href
Browse files Browse the repository at this point in the history
Implement DOC209 (Use 'see href' correctly)
  • Loading branch information
sharwell authored Oct 8, 2018
2 parents 213f609 + 3d55f6c commit 489c065
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 0 deletions.
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.PortabilityRules
{
using DocumentationAnalyzers.Test.PortabilityRules;

public class DOC209CSharp7UnitTests : DOC209UnitTests
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// 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.PortabilityRules
{
using System.Threading.Tasks;
using Xunit;
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.PortabilityRules.DOC209UseSeeHrefCorrectly, Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;

public class DOC209UnitTests
{
[Fact]
public async Task TestAbsoluteUriAsync()
{
var testCode = $@"
/// <summary>
/// <see href=""https://github.com""/>
/// <see href=""https://github.com""></see>
/// </summary>
/// <seealso href=""https://github.com""/>
/// <seealso href=""https://github.com""></see>
class TestClass
{{
}}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestRelativeUriAsync()
{
var testCode = $@"
/// <summary>
/// <see href=""docs/index.md""/>
/// <see href=""docs/index.md""></see>
/// </summary>
/// <seealso href=""docs/index.md""/>
/// <seealso href=""docs/index.md""></see>
class TestClass
{{
}}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestEscapesAllowedAsync()
{
var testCode = @"
/// <summary>
/// <see href=""https://gith&#117;b.com""/>
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestEmptyAndFullElementsValidatedAsync()
{
var testCode = @"
/// <summary>
/// <see [|href|]=""https://""/>
/// <see [|href|]=""https://""></see>
/// </summary>
/// <seealso [|href|]=""https://""/>
/// <seealso [|href|]=""https://""></see>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestOtherAttributesIgnoredAsync()
{
var testCode = @"
/// <summary>
/// <see Href=""https://""/>
/// <see x:href=""https://""/>
/// <see name=""https://""/>
/// <see cref=""System.String""/>
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestOtherElementsIgnoredAsync()
{
var testCode = @"
/// <summary>
/// <p:see href=""https://""/>
/// </summary>
/// <p:seealso href=""https://""/>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal static class XmlCommentHelper
internal const string FileAttributeName = "file";
internal const string PathAttributeName = "path";
internal const string CrefArgumentName = "cref";
internal const string HrefArgumentName = "href";
internal const string NameArgumentName = "name";
internal const string LangwordArgumentName = "langword";
internal const string TypeAttributeName = "type";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// 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.PortabilityRules
{
using System;
using System.Collections.Immutable;
using System.Linq;
using DocumentationAnalyzers.Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
[NoCodeFix("Cannot convert an invalid URI to one intended by the author.")]
internal class DOC209UseSeeHrefCorrectly : DiagnosticAnalyzer
{
/// <summary>
/// The ID for diagnostics produced by the <see cref="DOC209UseSeeHrefCorrectly"/> analyzer.
/// </summary>
public const string DiagnosticId = "DOC209";
private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC209.md";

private static readonly LocalizableString Title = new LocalizableResourceString(nameof(PortabilityResources.DOC209Title), PortabilityResources.ResourceManager, typeof(PortabilityResources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(PortabilityResources.DOC209MessageFormat), PortabilityResources.ResourceManager, typeof(PortabilityResources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(PortabilityResources.DOC209Description), PortabilityResources.ResourceManager, typeof(PortabilityResources));

private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.PortabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(Descriptor);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterSyntaxNodeAction(HandleXmlNodeSyntax, SyntaxKind.XmlElement, SyntaxKind.XmlEmptyElement);
}

private static void HandleXmlNodeSyntax(SyntaxNodeAnalysisContext context)
{
var xmlNodeSyntax = (XmlNodeSyntax)context.Node;
var name = xmlNodeSyntax.GetName();
if (name is null || name.Prefix != null)
{
return;
}

if (name.LocalName.ValueText != XmlCommentHelper.SeeXmlTag
&& name.LocalName.ValueText != XmlCommentHelper.SeeAlsoXmlTag)
{
return;
}

SyntaxList<XmlAttributeSyntax> attributes;
if (xmlNodeSyntax is XmlEmptyElementSyntax xmlEmptyElement)
{
attributes = xmlEmptyElement.Attributes;
}
else
{
attributes = ((XmlElementSyntax)xmlNodeSyntax).StartTag.Attributes;
}

foreach (var attribute in attributes)
{
if (attribute.Name is null || attribute.Name.Prefix != null)
{
continue;
}

if (attribute.Name.LocalName.ValueText != XmlCommentHelper.HrefArgumentName)
{
continue;
}

var text = ((XmlTextAttributeSyntax)attribute).TextTokens;
string valueText;
if (text.Count == 1)
{
valueText = text[0].ValueText;
}
else
{
valueText = string.Join(string.Empty, text.Select(textToken => textToken.ValueText));
}

if (Uri.TryCreate(valueText, System.UriKind.RelativeOrAbsolute, out _))
{
continue;
}

context.ReportDiagnostic(Diagnostic.Create(Descriptor, attribute.Name.LocalName.GetLocation()));
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,13 @@
<data name="DOC207Title" xml:space="preserve">
<value>Use 'see langword' correctly</value>
</data>
<data name="DOC209Description" xml:space="preserve">
<value>'href' attribute value should be a URI</value>
</data>
<data name="DOC209MessageFormat" xml:space="preserve">
<value>'href' attribute value should be a URI</value>
</data>
<data name="DOC209Title" xml:space="preserve">
<value>Use 'see href' correctly</value>
</data>
</root>
32 changes: 32 additions & 0 deletions docs/DOC209.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# DOC209

<table>
<tr>
<td>TypeName</td>
<td>DOC209UseSeeHrefCorrectly</td>
</tr>
<tr>
<td>CheckId</td>
<td>DOC209</td>
</tr>
<tr>
<td>Category</td>
<td>Portability Rules</td>
</tr>
</table>

## Cause

The documentation contains a `<see href="..."/>` or `<seealso href="..."/>` element with an unrecognized URI.

## Rule description

*TODO*

## How to fix violations

*TODO*

## How to suppress violations

*TODO*
1 change: 1 addition & 0 deletions docs/PortabilityRules.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Identifier | Name | Description
[DOC203](DOC203.md) | UseBlockElementsCorrectly | The documentation contains a block element where a section or inline element was expected.
[DOC204](DOC204.md) | UseInlineElementsCorrectly | The documentation contains an inline element where a section or block element was expected.
[DOC207](DOC207.md) | UseSeeLangwordCorrectly | The documentation contains a `<see langword="..."/>` element with an unrecognized keyword.
[DOC209](DOC209.md) | UseSeeHrefCorrectly | The documentation contains a `<see href="..."/>` element with an unrecognized URI.

0 comments on commit 489c065

Please sign in to comment.