Skip to content

Commit 489c065

Browse files
authored
Merge pull request #68 from sharwell/verify-href
Implement DOC209 (Use 'see href' correctly)
2 parents 213f609 + 3d55f6c commit 489c065

File tree

8 files changed

+297
-0
lines changed

8 files changed

+297
-0
lines changed
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.PortabilityRules
5+
{
6+
using DocumentationAnalyzers.Test.PortabilityRules;
7+
8+
public class DOC209CSharp7UnitTests : DOC209UnitTests
9+
{
10+
}
11+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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.PortabilityRules
5+
{
6+
using System.Threading.Tasks;
7+
using Xunit;
8+
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.PortabilityRules.DOC209UseSeeHrefCorrectly, Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;
9+
10+
public class DOC209UnitTests
11+
{
12+
[Fact]
13+
public async Task TestAbsoluteUriAsync()
14+
{
15+
var testCode = $@"
16+
/// <summary>
17+
/// <see href=""https://github.com""/>
18+
/// <see href=""https://github.com""></see>
19+
/// </summary>
20+
/// <seealso href=""https://github.com""/>
21+
/// <seealso href=""https://github.com""></see>
22+
class TestClass
23+
{{
24+
}}
25+
";
26+
27+
await Verify.VerifyAnalyzerAsync(testCode);
28+
}
29+
30+
[Fact]
31+
public async Task TestRelativeUriAsync()
32+
{
33+
var testCode = $@"
34+
/// <summary>
35+
/// <see href=""docs/index.md""/>
36+
/// <see href=""docs/index.md""></see>
37+
/// </summary>
38+
/// <seealso href=""docs/index.md""/>
39+
/// <seealso href=""docs/index.md""></see>
40+
class TestClass
41+
{{
42+
}}
43+
";
44+
45+
await Verify.VerifyAnalyzerAsync(testCode);
46+
}
47+
48+
[Fact]
49+
public async Task TestEscapesAllowedAsync()
50+
{
51+
var testCode = @"
52+
/// <summary>
53+
/// <see href=""https://gith&#117;b.com""/>
54+
/// </summary>
55+
class TestClass
56+
{
57+
}
58+
";
59+
60+
await Verify.VerifyAnalyzerAsync(testCode);
61+
}
62+
63+
[Fact]
64+
public async Task TestEmptyAndFullElementsValidatedAsync()
65+
{
66+
var testCode = @"
67+
/// <summary>
68+
/// <see [|href|]=""https://""/>
69+
/// <see [|href|]=""https://""></see>
70+
/// </summary>
71+
/// <seealso [|href|]=""https://""/>
72+
/// <seealso [|href|]=""https://""></see>
73+
class TestClass
74+
{
75+
}
76+
";
77+
78+
await Verify.VerifyAnalyzerAsync(testCode);
79+
}
80+
81+
[Fact]
82+
public async Task TestOtherAttributesIgnoredAsync()
83+
{
84+
var testCode = @"
85+
/// <summary>
86+
/// <see Href=""https://""/>
87+
/// <see x:href=""https://""/>
88+
/// <see name=""https://""/>
89+
/// <see cref=""System.String""/>
90+
/// </summary>
91+
class TestClass
92+
{
93+
}
94+
";
95+
96+
await Verify.VerifyAnalyzerAsync(testCode);
97+
}
98+
99+
[Fact]
100+
public async Task TestOtherElementsIgnoredAsync()
101+
{
102+
var testCode = @"
103+
/// <summary>
104+
/// <p:see href=""https://""/>
105+
/// </summary>
106+
/// <p:seealso href=""https://""/>
107+
class TestClass
108+
{
109+
}
110+
";
111+
112+
await Verify.VerifyAnalyzerAsync(testCode);
113+
}
114+
}
115+
}

DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ internal static class XmlCommentHelper
4242
internal const string FileAttributeName = "file";
4343
internal const string PathAttributeName = "path";
4444
internal const string CrefArgumentName = "cref";
45+
internal const string HrefArgumentName = "href";
4546
internal const string NameArgumentName = "name";
4647
internal const string LangwordArgumentName = "langword";
4748
internal const string TypeAttributeName = "type";
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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.PortabilityRules
5+
{
6+
using System;
7+
using System.Collections.Immutable;
8+
using System.Linq;
9+
using DocumentationAnalyzers.Helpers;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.CodeAnalysis.CSharp.Syntax;
13+
using Microsoft.CodeAnalysis.Diagnostics;
14+
15+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
16+
[NoCodeFix("Cannot convert an invalid URI to one intended by the author.")]
17+
internal class DOC209UseSeeHrefCorrectly : DiagnosticAnalyzer
18+
{
19+
/// <summary>
20+
/// The ID for diagnostics produced by the <see cref="DOC209UseSeeHrefCorrectly"/> analyzer.
21+
/// </summary>
22+
public const string DiagnosticId = "DOC209";
23+
private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC209.md";
24+
25+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(PortabilityResources.DOC209Title), PortabilityResources.ResourceManager, typeof(PortabilityResources));
26+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(PortabilityResources.DOC209MessageFormat), PortabilityResources.ResourceManager, typeof(PortabilityResources));
27+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(PortabilityResources.DOC209Description), PortabilityResources.ResourceManager, typeof(PortabilityResources));
28+
29+
private static readonly DiagnosticDescriptor Descriptor =
30+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.PortabilityRules, DiagnosticSeverity.Warning, AnalyzerConstants.EnabledByDefault, Description, HelpLink);
31+
32+
/// <inheritdoc/>
33+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
34+
= ImmutableArray.Create(Descriptor);
35+
36+
public override void Initialize(AnalysisContext context)
37+
{
38+
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
39+
context.EnableConcurrentExecution();
40+
41+
context.RegisterSyntaxNodeAction(HandleXmlNodeSyntax, SyntaxKind.XmlElement, SyntaxKind.XmlEmptyElement);
42+
}
43+
44+
private static void HandleXmlNodeSyntax(SyntaxNodeAnalysisContext context)
45+
{
46+
var xmlNodeSyntax = (XmlNodeSyntax)context.Node;
47+
var name = xmlNodeSyntax.GetName();
48+
if (name is null || name.Prefix != null)
49+
{
50+
return;
51+
}
52+
53+
if (name.LocalName.ValueText != XmlCommentHelper.SeeXmlTag
54+
&& name.LocalName.ValueText != XmlCommentHelper.SeeAlsoXmlTag)
55+
{
56+
return;
57+
}
58+
59+
SyntaxList<XmlAttributeSyntax> attributes;
60+
if (xmlNodeSyntax is XmlEmptyElementSyntax xmlEmptyElement)
61+
{
62+
attributes = xmlEmptyElement.Attributes;
63+
}
64+
else
65+
{
66+
attributes = ((XmlElementSyntax)xmlNodeSyntax).StartTag.Attributes;
67+
}
68+
69+
foreach (var attribute in attributes)
70+
{
71+
if (attribute.Name is null || attribute.Name.Prefix != null)
72+
{
73+
continue;
74+
}
75+
76+
if (attribute.Name.LocalName.ValueText != XmlCommentHelper.HrefArgumentName)
77+
{
78+
continue;
79+
}
80+
81+
var text = ((XmlTextAttributeSyntax)attribute).TextTokens;
82+
string valueText;
83+
if (text.Count == 1)
84+
{
85+
valueText = text[0].ValueText;
86+
}
87+
else
88+
{
89+
valueText = string.Join(string.Empty, text.Select(textToken => textToken.ValueText));
90+
}
91+
92+
if (Uri.TryCreate(valueText, System.UriKind.RelativeOrAbsolute, out _))
93+
{
94+
continue;
95+
}
96+
97+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, attribute.Name.LocalName.GetLocation()));
98+
}
99+
}
100+
}
101+
}

DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs

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

DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,13 @@
186186
<data name="DOC207Title" xml:space="preserve">
187187
<value>Use 'see langword' correctly</value>
188188
</data>
189+
<data name="DOC209Description" xml:space="preserve">
190+
<value>'href' attribute value should be a URI</value>
191+
</data>
192+
<data name="DOC209MessageFormat" xml:space="preserve">
193+
<value>'href' attribute value should be a URI</value>
194+
</data>
195+
<data name="DOC209Title" xml:space="preserve">
196+
<value>Use 'see href' correctly</value>
197+
</data>
189198
</root>

docs/DOC209.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# DOC209
2+
3+
<table>
4+
<tr>
5+
<td>TypeName</td>
6+
<td>DOC209UseSeeHrefCorrectly</td>
7+
</tr>
8+
<tr>
9+
<td>CheckId</td>
10+
<td>DOC209</td>
11+
</tr>
12+
<tr>
13+
<td>Category</td>
14+
<td>Portability Rules</td>
15+
</tr>
16+
</table>
17+
18+
## Cause
19+
20+
The documentation contains a `<see href="..."/>` or `<seealso href="..."/>` element with an unrecognized URI.
21+
22+
## Rule description
23+
24+
*TODO*
25+
26+
## How to fix violations
27+
28+
*TODO*
29+
30+
## How to suppress violations
31+
32+
*TODO*

docs/PortabilityRules.md

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

0 commit comments

Comments
 (0)