Skip to content

Commit

Permalink
Merge pull request #67 from sharwell/verify-langword
Browse files Browse the repository at this point in the history
Implement DOC207 (Use 'see langword' correctly)
  • Loading branch information
sharwell authored Oct 8, 2018
2 parents 4a22934 + bdfa09a commit 213f609
Show file tree
Hide file tree
Showing 8 changed files with 306 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 DOC207CSharp7UnitTests : DOC207UnitTests
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// 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.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp;
using Xunit;
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.PortabilityRules.DOC207UseSeeLangwordCorrectly, Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;

public class DOC207UnitTests
{
public static IEnumerable<object[]> Keywords
{
get
{
foreach (var keywordKind in SyntaxFacts.GetKeywordKinds())
{
yield return new[] { SyntaxFacts.GetText(keywordKind) };
}
}
}

[Theory]
[MemberData(nameof(Keywords))]
public async Task TestRecognizedKeywordsAsync(string keyword)
{
var testCode = $@"
/// <summary>
/// <see langword=""{keyword}""/>
/// <see langword=""{keyword}""></see>
/// </summary>
class TestClass
{{
}}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestSpacesNotAllowedAsync()
{
var testCode = @"
/// <summary>
/// <see langword=""null""/>
/// <see [|langword|]="" null""/>
/// <see [|langword|]=""null ""/>
/// <see [|langword|]="" null ""/>
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestEscapesAllowedAsync()
{
var testCode = @"
/// <summary>
/// <see langword=""n&#117;ll""/>
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestEmptyAndFullElementsValidatedAsync()
{
var testCode = @"
/// <summary>
/// <see [|langword|]=""not a keyword""/>
/// <see [|langword|]=""not a keyword""></see>
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestOtherAttributesIgnoredAsync()
{
var testCode = @"
/// <summary>
/// <see Langword=""not a keyword""/>
/// <see x:langword=""not a keyword""/>
/// <see name=""not a keyword""/>
/// <see cref=""System.String""/>
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestOtherElementsIgnoredAsync()
{
var testCode = @"
/// <summary>
/// <p:see langword=""not a keyword""/>
/// </summary>
///
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ internal static class XmlCommentHelper
internal const string PathAttributeName = "path";
internal const string CrefArgumentName = "cref";
internal const string NameArgumentName = "name";
internal const string LangwordArgumentName = "langword";
internal const string TypeAttributeName = "type";

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// 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.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("https://github.com/DotNetAnalyzers/DocumentationAnalyzers/issues/66")]
internal class DOC207UseSeeLangwordCorrectly : DiagnosticAnalyzer
{
/// <summary>
/// The ID for diagnostics produced by the <see cref="DOC207UseSeeLangwordCorrectly"/> analyzer.
/// </summary>
public const string DiagnosticId = "DOC207";
private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC207.md";

private static readonly LocalizableString Title = new LocalizableResourceString(nameof(PortabilityResources.DOC207Title), PortabilityResources.ResourceManager, typeof(PortabilityResources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(PortabilityResources.DOC207MessageFormat), PortabilityResources.ResourceManager, typeof(PortabilityResources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(PortabilityResources.DOC207Description), 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)
{
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.LangwordArgumentName)
{
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 (SyntaxFacts.GetKeywordKind(valueText) != SyntaxKind.None
|| SyntaxFacts.GetContextualKeywordKind(valueText) != SyntaxKind.None)
{
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 @@ -177,4 +177,13 @@
<data name="DOC204Title" xml:space="preserve">
<value>Use inline elements correctly</value>
</data>
<data name="DOC207Description" xml:space="preserve">
<value>'langword' attribute value should be a language keyword</value>
</data>
<data name="DOC207MessageFormat" xml:space="preserve">
<value>'langword' attribute value should be a language keyword</value>
</data>
<data name="DOC207Title" xml:space="preserve">
<value>Use 'see langword' correctly</value>
</data>
</root>
32 changes: 32 additions & 0 deletions docs/DOC207.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# DOC207

<table>
<tr>
<td>TypeName</td>
<td>DOC207UseSeeLangwordCorrectly</td>
</tr>
<tr>
<td>CheckId</td>
<td>DOC207</td>
</tr>
<tr>
<td>Category</td>
<td>Portability Rules</td>
</tr>
</table>

## Cause

The documentation contains a `<see langword="..."/>` element with an unrecognized keyword.

## 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 @@ -9,3 +9,4 @@ Identifier | Name | Description
[DOC202](DOC202.md) | UseSectionElementsCorrectly | The documentation contains a section element where a block or inline element was expected.
[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.

0 comments on commit 213f609

Please sign in to comment.