diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC209CSharp7UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC209CSharp7UnitTests.cs new file mode 100644 index 0000000..5543c2a --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC209CSharp7UnitTests.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 DOC209CSharp7UnitTests : DOC209UnitTests + { + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC209UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC209UnitTests.cs new file mode 100644 index 0000000..1b67486 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC209UnitTests.cs @@ -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; + + public class DOC209UnitTests + { + [Fact] + public async Task TestAbsoluteUriAsync() + { + var testCode = $@" +/// +/// +/// +/// +/// +/// +class TestClass +{{ +}} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task TestRelativeUriAsync() + { + 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); + } + + [Fact] + public async Task TestOtherAttributesIgnoredAsync() + { + var testCode = @" +/// +/// +/// +/// +/// +/// +class TestClass +{ +} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task TestOtherElementsIgnoredAsync() + { + var testCode = @" +/// +/// +/// +/// +class TestClass +{ +} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs b/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs index ac1f5b1..3d9e794 100644 --- a/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs +++ b/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs @@ -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"; diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC209UseSeeHrefCorrectly.cs b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC209UseSeeHrefCorrectly.cs new file mode 100644 index 0000000..657d046 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC209UseSeeHrefCorrectly.cs @@ -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 + { + /// + /// The ID for diagnostics produced by the analyzer. + /// + 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); + + /// + 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 + && name.LocalName.ValueText != XmlCommentHelper.SeeAlsoXmlTag) + { + return; + } + + SyntaxList 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())); + } + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs index 8df08fa..a90dc8a 100644 --- a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs @@ -267,5 +267,32 @@ internal static string DOC207Title { return ResourceManager.GetString("DOC207Title", resourceCulture); } } + + /// + /// Looks up a localized string similar to 'href' attribute value should be a URI. + /// + internal static string DOC209Description { + get { + return ResourceManager.GetString("DOC209Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 'href' attribute value should be a URI. + /// + internal static string DOC209MessageFormat { + get { + return ResourceManager.GetString("DOC209MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use 'see href' correctly. + /// + internal static string DOC209Title { + get { + return ResourceManager.GetString("DOC209Title", resourceCulture); + } + } } } diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx index 8ebdc11..7334a6e 100644 --- a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx @@ -186,4 +186,13 @@ Use 'see langword' correctly + + 'href' attribute value should be a URI + + + 'href' attribute value should be a URI + + + Use 'see href' correctly + \ No newline at end of file diff --git a/docs/DOC209.md b/docs/DOC209.md new file mode 100644 index 0000000..5074f2f --- /dev/null +++ b/docs/DOC209.md @@ -0,0 +1,32 @@ +# DOC209 + + + + + + + + + + + + + + +
TypeNameDOC209UseSeeHrefCorrectly
CheckIdDOC209
CategoryPortability Rules
+ +## Cause + +The documentation contains a `` or `` element with an unrecognized URI. + +## Rule description + +*TODO* + +## How to fix violations + +*TODO* + +## How to suppress violations + +*TODO* diff --git a/docs/PortabilityRules.md b/docs/PortabilityRules.md index aa51633..5a7b2b5 100644 --- a/docs/PortabilityRules.md +++ b/docs/PortabilityRules.md @@ -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 `` element with an unrecognized keyword. +[DOC209](DOC209.md) | UseSeeHrefCorrectly | The documentation contains a `` element with an unrecognized URI.