diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC207CSharp7UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC207CSharp7UnitTests.cs new file mode 100644 index 0000000..343455f --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC207CSharp7UnitTests.cs @@ -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 + { + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC207UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC207UnitTests.cs new file mode 100644 index 0000000..e3d0b43 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC207UnitTests.cs @@ -0,0 +1,91 @@ +// 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; + + public class DOC207UnitTests + { + public static IEnumerable 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 = $@" +/// +/// +/// +/// +class TestClass +{{ +}} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task TestSpacesNotAllowedAsync() + { + var testCode = @" +/// +/// +/// +/// +/// +/// +class TestClass +{ +} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task TestEscapesAllowedAsync() + { + var testCode = @" +/// +/// +/// +class TestClass +{ +} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task TestEmptyAndFullElementsValidatedAsync() + { + var testCode = @" +/// +/// +/// +/// +class TestClass +{ +} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs b/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs index 61177a3..ac1f5b1 100644 --- a/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs +++ b/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs @@ -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"; /// diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC207UseSeeLangwordCorrectly.cs b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC207UseSeeLangwordCorrectly.cs new file mode 100644 index 0000000..ffccbb8 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC207UseSeeLangwordCorrectly.cs @@ -0,0 +1,109 @@ +// 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 + { + /// + /// The ID for diagnostics produced by the analyzer. + /// + 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); + + /// + public override ImmutableArray 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 attributes; + if (xmlNodeSyntax is XmlEmptyElementSyntax xmlEmptyElement) + { + attributes = xmlEmptyElement.Attributes; + } + else if (xmlNodeSyntax is XmlElementSyntax xmlElement) + { + attributes = xmlElement.StartTag.Attributes; + } + else + { + return; + } + + foreach (var attribute in attributes) + { + if (attribute.Name is null || attribute.Name.Prefix != null) + { + continue; + } + + if (attribute.Name.LocalName.ValueText != XmlCommentHelper.LangwordArgumentName) + { + continue; + } + + if (!(attribute is XmlTextAttributeSyntax xmlTextAttribute)) + { + continue; + } + + var text = xmlTextAttribute.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())); + } + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs index dd8bd35..1791a08 100644 --- a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs @@ -240,5 +240,32 @@ internal static string DOC204Title { return ResourceManager.GetString("DOC204Title", resourceCulture); } } + + /// + /// Looks up a localized string similar to Use 'see langword' correctly. + /// + internal static string DOC207Description { + get { + return ResourceManager.GetString("DOC207Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use 'see langword' correctly. + /// + internal static string DOC207MessageFormat { + get { + return ResourceManager.GetString("DOC207MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use 'see langword' correctly. + /// + internal static string DOC207Title { + get { + return ResourceManager.GetString("DOC207Title", resourceCulture); + } + } } } diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx index a2bd94b..6a31448 100644 --- a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx @@ -177,4 +177,13 @@ Use inline elements correctly + + Use 'see langword' correctly + + + Use 'see langword' correctly + + + Use 'see langword' correctly + \ No newline at end of file diff --git a/docs/DOC207.md b/docs/DOC207.md new file mode 100644 index 0000000..1e70679 --- /dev/null +++ b/docs/DOC207.md @@ -0,0 +1,32 @@ +# DOC207 + + + + + + + + + + + + + + +
TypeNameDOC207UseSeeLangwordCorrectly
CheckIdDOC207
CategoryPortability Rules
+ +## Cause + +The documentation contains a `` element with an unrecognized keyword. + +## Rule description + +*TODO* + +## How to fix violations + +*TODO* + +## How to suppress violations + +*TODO* diff --git a/docs/PortabilityRules.md b/docs/PortabilityRules.md index 914d73e..aa51633 100644 --- a/docs/PortabilityRules.md +++ b/docs/PortabilityRules.md @@ -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 `` element with an unrecognized keyword.