Skip to content

Commit 213f609

Browse files
authored
Merge pull request #67 from sharwell/verify-langword
Implement DOC207 (Use 'see langword' correctly)
2 parents 4a22934 + bdfa09a commit 213f609

File tree

8 files changed

+306
-0
lines changed

8 files changed

+306
-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 DOC207CSharp7UnitTests : DOC207UnitTests
9+
{
10+
}
11+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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.Collections.Generic;
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Xunit;
10+
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.PortabilityRules.DOC207UseSeeLangwordCorrectly, Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;
11+
12+
public class DOC207UnitTests
13+
{
14+
public static IEnumerable<object[]> Keywords
15+
{
16+
get
17+
{
18+
foreach (var keywordKind in SyntaxFacts.GetKeywordKinds())
19+
{
20+
yield return new[] { SyntaxFacts.GetText(keywordKind) };
21+
}
22+
}
23+
}
24+
25+
[Theory]
26+
[MemberData(nameof(Keywords))]
27+
public async Task TestRecognizedKeywordsAsync(string keyword)
28+
{
29+
var testCode = $@"
30+
/// <summary>
31+
/// <see langword=""{keyword}""/>
32+
/// <see langword=""{keyword}""></see>
33+
/// </summary>
34+
class TestClass
35+
{{
36+
}}
37+
";
38+
39+
await Verify.VerifyAnalyzerAsync(testCode);
40+
}
41+
42+
[Fact]
43+
public async Task TestSpacesNotAllowedAsync()
44+
{
45+
var testCode = @"
46+
/// <summary>
47+
/// <see langword=""null""/>
48+
/// <see [|langword|]="" null""/>
49+
/// <see [|langword|]=""null ""/>
50+
/// <see [|langword|]="" null ""/>
51+
/// </summary>
52+
class TestClass
53+
{
54+
}
55+
";
56+
57+
await Verify.VerifyAnalyzerAsync(testCode);
58+
}
59+
60+
[Fact]
61+
public async Task TestEscapesAllowedAsync()
62+
{
63+
var testCode = @"
64+
/// <summary>
65+
/// <see langword=""n&#117;ll""/>
66+
/// </summary>
67+
class TestClass
68+
{
69+
}
70+
";
71+
72+
await Verify.VerifyAnalyzerAsync(testCode);
73+
}
74+
75+
[Fact]
76+
public async Task TestEmptyAndFullElementsValidatedAsync()
77+
{
78+
var testCode = @"
79+
/// <summary>
80+
/// <see [|langword|]=""not a keyword""/>
81+
/// <see [|langword|]=""not a keyword""></see>
82+
/// </summary>
83+
class TestClass
84+
{
85+
}
86+
";
87+
88+
await Verify.VerifyAnalyzerAsync(testCode);
89+
}
90+
91+
[Fact]
92+
public async Task TestOtherAttributesIgnoredAsync()
93+
{
94+
var testCode = @"
95+
/// <summary>
96+
/// <see Langword=""not a keyword""/>
97+
/// <see x:langword=""not a keyword""/>
98+
/// <see name=""not a keyword""/>
99+
/// <see cref=""System.String""/>
100+
/// </summary>
101+
class TestClass
102+
{
103+
}
104+
";
105+
106+
await Verify.VerifyAnalyzerAsync(testCode);
107+
}
108+
109+
[Fact]
110+
public async Task TestOtherElementsIgnoredAsync()
111+
{
112+
var testCode = @"
113+
/// <summary>
114+
/// <p:see langword=""not a keyword""/>
115+
/// </summary>
116+
///
117+
class TestClass
118+
{
119+
}
120+
";
121+
122+
await Verify.VerifyAnalyzerAsync(testCode);
123+
}
124+
}
125+
}

DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ internal static class XmlCommentHelper
4343
internal const string PathAttributeName = "path";
4444
internal const string CrefArgumentName = "cref";
4545
internal const string NameArgumentName = "name";
46+
internal const string LangwordArgumentName = "langword";
4647
internal const string TypeAttributeName = "type";
4748

4849
/// <summary>
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
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.Collections.Immutable;
7+
using System.Linq;
8+
using DocumentationAnalyzers.Helpers;
9+
using Microsoft.CodeAnalysis;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
using Microsoft.CodeAnalysis.Diagnostics;
13+
14+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
15+
[NoCodeFix("https://github.com/DotNetAnalyzers/DocumentationAnalyzers/issues/66")]
16+
internal class DOC207UseSeeLangwordCorrectly : DiagnosticAnalyzer
17+
{
18+
/// <summary>
19+
/// The ID for diagnostics produced by the <see cref="DOC207UseSeeLangwordCorrectly"/> analyzer.
20+
/// </summary>
21+
public const string DiagnosticId = "DOC207";
22+
private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC207.md";
23+
24+
private static readonly LocalizableString Title = new LocalizableResourceString(nameof(PortabilityResources.DOC207Title), PortabilityResources.ResourceManager, typeof(PortabilityResources));
25+
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(PortabilityResources.DOC207MessageFormat), PortabilityResources.ResourceManager, typeof(PortabilityResources));
26+
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(PortabilityResources.DOC207Description), PortabilityResources.ResourceManager, typeof(PortabilityResources));
27+
28+
private static readonly DiagnosticDescriptor Descriptor =
29+
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.PortabilityRules, DiagnosticSeverity.Warning, 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(HandleXmlNodeSyntax, SyntaxKind.XmlElement, SyntaxKind.XmlEmptyElement);
41+
}
42+
43+
private static void HandleXmlNodeSyntax(SyntaxNodeAnalysisContext context)
44+
{
45+
var xmlNodeSyntax = (XmlNodeSyntax)context.Node;
46+
var name = xmlNodeSyntax.GetName();
47+
if (name is null || name.Prefix != null)
48+
{
49+
return;
50+
}
51+
52+
if (name.LocalName.ValueText != XmlCommentHelper.SeeXmlTag)
53+
{
54+
return;
55+
}
56+
57+
SyntaxList<XmlAttributeSyntax> attributes;
58+
if (xmlNodeSyntax is XmlEmptyElementSyntax xmlEmptyElement)
59+
{
60+
attributes = xmlEmptyElement.Attributes;
61+
}
62+
else
63+
{
64+
attributes = ((XmlElementSyntax)xmlNodeSyntax).StartTag.Attributes;
65+
}
66+
67+
foreach (var attribute in attributes)
68+
{
69+
if (attribute.Name is null || attribute.Name.Prefix != null)
70+
{
71+
continue;
72+
}
73+
74+
if (attribute.Name.LocalName.ValueText != XmlCommentHelper.LangwordArgumentName)
75+
{
76+
continue;
77+
}
78+
79+
var text = ((XmlTextAttributeSyntax)attribute).TextTokens;
80+
string valueText;
81+
if (text.Count == 1)
82+
{
83+
valueText = text[0].ValueText;
84+
}
85+
else
86+
{
87+
valueText = string.Join(string.Empty, text.Select(textToken => textToken.ValueText));
88+
}
89+
90+
if (SyntaxFacts.GetKeywordKind(valueText) != SyntaxKind.None
91+
|| SyntaxFacts.GetContextualKeywordKind(valueText) != SyntaxKind.None)
92+
{
93+
continue;
94+
}
95+
96+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, attribute.Name.LocalName.GetLocation()));
97+
}
98+
}
99+
}
100+
}

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
@@ -177,4 +177,13 @@
177177
<data name="DOC204Title" xml:space="preserve">
178178
<value>Use inline elements correctly</value>
179179
</data>
180+
<data name="DOC207Description" xml:space="preserve">
181+
<value>'langword' attribute value should be a language keyword</value>
182+
</data>
183+
<data name="DOC207MessageFormat" xml:space="preserve">
184+
<value>'langword' attribute value should be a language keyword</value>
185+
</data>
186+
<data name="DOC207Title" xml:space="preserve">
187+
<value>Use 'see langword' correctly</value>
188+
</data>
180189
</root>

docs/DOC207.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# DOC207
2+
3+
<table>
4+
<tr>
5+
<td>TypeName</td>
6+
<td>DOC207UseSeeLangwordCorrectly</td>
7+
</tr>
8+
<tr>
9+
<td>CheckId</td>
10+
<td>DOC207</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 langword="..."/>` element with an unrecognized keyword.
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
@@ -9,3 +9,4 @@ Identifier | Name | Description
99
[DOC202](DOC202.md) | UseSectionElementsCorrectly | The documentation contains a section element where a block or inline element was expected.
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.
12+
[DOC207](DOC207.md) | UseSeeLangwordCorrectly | The documentation contains a `<see langword="..."/>` element with an unrecognized keyword.

0 commit comments

Comments
 (0)