diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/StyleRules/DOC103CodeFixProvider.cs b/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/StyleRules/DOC103CodeFixProvider.cs
new file mode 100644
index 0000000..01dd849
--- /dev/null
+++ b/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/StyleRules/DOC103CodeFixProvider.cs
@@ -0,0 +1,118 @@
+// 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.StyleRules
+{
+ using System.Collections.Immutable;
+ using System.Composition;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using DocumentationAnalyzers.Helpers;
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CodeActions;
+ using Microsoft.CodeAnalysis.CodeFixes;
+ using Microsoft.CodeAnalysis.CSharp;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+ [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC103CodeFixProvider))]
+ [Shared]
+ internal class DOC103CodeFixProvider : CodeFixProvider
+ {
+ public override ImmutableArray FixableDiagnosticIds { get; }
+ = ImmutableArray.Create(DOC103UseXmlDocumentationSyntax.DiagnosticId);
+
+ public override FixAllProvider GetFixAllProvider()
+ => CustomFixAllProviders.BatchFixer;
+
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ foreach (var diagnostic in context.Diagnostics)
+ {
+ if (!FixableDiagnosticIds.Contains(diagnostic.Id))
+ {
+ continue;
+ }
+
+ context.RegisterCodeFix(
+ CodeAction.Create(
+ StyleResources.BlockLevelDocumentationCodeFix,
+ token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
+ nameof(DOC103CodeFixProvider)),
+ diagnostic);
+ }
+
+ return SpecializedTasks.CompletedTask;
+ }
+
+ private static async Task GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
+ {
+ SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+ SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);
+
+ var xmlElement = token.Parent.FirstAncestorOrSelf();
+ var oldStartToken = xmlElement.StartTag.Name.LocalName;
+
+ string newIdentifier;
+ switch (oldStartToken.ValueText)
+ {
+ case "p":
+ newIdentifier = XmlCommentHelper.ParaXmlTag;
+ break;
+
+ case "tt":
+ newIdentifier = XmlCommentHelper.CXmlTag;
+ break;
+
+ case "pre":
+ newIdentifier = XmlCommentHelper.CodeXmlTag;
+ break;
+
+ case "ul":
+ case "ol":
+ newIdentifier = XmlCommentHelper.ListXmlTag;
+ break;
+
+ default:
+ // Not handled
+ return document;
+ }
+
+ var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, newIdentifier, oldStartToken.TrailingTrivia);
+ var newXmlElement = xmlElement.ReplaceToken(oldStartToken, newStartToken);
+
+ var oldEndToken = newXmlElement.EndTag.Name.LocalName;
+ var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, newIdentifier, oldEndToken.TrailingTrivia);
+ newXmlElement = newXmlElement.ReplaceToken(oldEndToken, newEndToken);
+
+ if (newIdentifier == XmlCommentHelper.ListXmlTag)
+ {
+ // Add an attribute for the list kind
+ string listType = oldStartToken.ValueText == "ol" ? "number" : "bullet";
+ newXmlElement = newXmlElement.WithStartTag(newXmlElement.StartTag.AddAttributes(XmlSyntaxFactory.TextAttribute(XmlCommentHelper.TypeAttributeName, listType)));
+
+ // Replace each ... element with - ...
+ for (int i = 0; i < newXmlElement.Content.Count; i++)
+ {
+ if (newXmlElement.Content[i] is XmlElementSyntax childXmlElement
+ && childXmlElement.StartTag?.Name?.LocalName.ValueText == "li"
+ && childXmlElement.StartTag.Name.Prefix == null)
+ {
+ oldStartToken = childXmlElement.StartTag.Name.LocalName;
+ newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, XmlCommentHelper.ItemXmlTag, oldStartToken.TrailingTrivia);
+ var newChildXmlElement = childXmlElement.ReplaceToken(oldStartToken, newStartToken);
+
+ oldEndToken = newChildXmlElement.EndTag.Name.LocalName;
+ newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, XmlCommentHelper.ItemXmlTag, oldEndToken.TrailingTrivia);
+ newChildXmlElement = newChildXmlElement.ReplaceToken(oldEndToken, newEndToken);
+
+ newChildXmlElement = newChildXmlElement.WithContent(XmlSyntaxFactory.List(XmlSyntaxFactory.Element(XmlCommentHelper.DescriptionXmlTag, newChildXmlElement.Content)));
+
+ newXmlElement = newXmlElement.ReplaceNode(childXmlElement, newChildXmlElement);
+ }
+ }
+ }
+
+ return document.WithSyntaxRoot(root.ReplaceNode(xmlElement, newXmlElement));
+ }
+ }
+}
diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/StyleRules/DOC103CSharp7UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/StyleRules/DOC103CSharp7UnitTests.cs
new file mode 100644
index 0000000..e826ff9
--- /dev/null
+++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/StyleRules/DOC103CSharp7UnitTests.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.StyleRules
+{
+ using DocumentationAnalyzers.Test.StyleRules;
+
+ public class DOC103CSharp7UnitTests : DOC103UnitTests
+ {
+ }
+}
diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test/StyleRules/DOC103UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test/StyleRules/DOC103UnitTests.cs
new file mode 100644
index 0000000..6b4a74a
--- /dev/null
+++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test/StyleRules/DOC103UnitTests.cs
@@ -0,0 +1,201 @@
+// 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.StyleRules
+{
+ using System.Collections.Generic;
+ using System.Threading;
+ using System.Threading.Tasks;
+ using DocumentationAnalyzers.StyleRules;
+ using Microsoft.CodeAnalysis.CodeFixes;
+ using Microsoft.CodeAnalysis.Diagnostics;
+ using Microsoft.CodeAnalysis.Testing;
+ using Xunit;
+ using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier;
+
+ ///
+ /// This class contains unit tests for .
+ ///
+ public class DOC103UnitTests
+ {
+ [Fact]
+ public async Task TestHtmlParagraphAsync()
+ {
+ var testCode = @"
+///
+/// <[|p|]>This is a paragraph.
+///
+class TestClass { }
+";
+ var fixedCode = @"
+///
+/// This is a paragraph.
+///
+class TestClass { }
+";
+
+ await Verify.VerifyCodeFixAsync(testCode, fixedCode);
+ }
+
+ [Fact]
+ public async Task TestHtmlParagraphWithAttributeAsync()
+ {
+ var testCode = @"
+///
+/// <[|p|] attr=""value"">This is a paragraph.
+///
+class TestClass { }
+";
+ var fixedCode = @"
+///
+/// This is a paragraph.
+///
+class TestClass { }
+";
+
+ await Verify.VerifyCodeFixAsync(testCode, fixedCode);
+ }
+
+ [Fact]
+ public async Task TestPrefixAsync()
+ {
+ var testCode = @"
+///
+/// This is a paragraph.
+///
+class TestClass { }
+";
+
+ await Verify.VerifyAnalyzerAsync(testCode);
+ }
+
+ [Fact]
+ public async Task TestHtmlCodeAsync()
+ {
+ var testCode = @"
+///
+/// This is <[|tt|]>code.
+///
+class TestClass { }
+";
+ var fixedCode = @"
+///
+/// This is code.
+///
+class TestClass { }
+";
+
+ await Verify.VerifyCodeFixAsync(testCode, fixedCode);
+ }
+
+ [Fact]
+ public async Task TestHtmlCodeBlockAsync()
+ {
+ var testCode = @"
+///
+/// This is a code block:
+/// <[|pre|]>
+/// code goes here
+/// more code here
+///
+///
+class TestClass { }
+";
+ var fixedCode = @"
+///
+/// This is a code block:
+///
+/// code goes here
+/// more code here
+///
+///
+class TestClass { }
+";
+
+ await Verify.VerifyCodeFixAsync(testCode, fixedCode);
+ }
+
+ [Fact]
+ public async Task TestHtmlOrderedListAsync()
+ {
+ var testCode = @"
+///
+/// This is an ordered list:
+/// <[|ol|]>
+/// Item 1
+/// Item 2
+///
+///
+class TestClass { }
+";
+ var fixedCode = @"
+///
+/// This is an ordered list:
+///
+/// - Item 1
+/// - Item 2
+///
+///
+class TestClass { }
+";
+
+ await Verify.VerifyCodeFixAsync(testCode, fixedCode);
+ }
+
+ [Fact]
+ public async Task TestHtmlUnorderedListAsync()
+ {
+ var testCode = @"
+///
+/// This is an ordered list:
+/// <[|ul|]>
+/// Item 1
+/// Item 2
+///
+///
+class TestClass { }
+";
+ var fixedCode = @"
+///
+/// This is an ordered list:
+///
+/// - Item 1
+/// - Item 2
+///
+///
+class TestClass { }
+";
+
+ await Verify.VerifyCodeFixAsync(testCode, fixedCode);
+ }
+
+ [Fact]
+ public async Task TestHtmlUnorderedListMultilineItemAsync()
+ {
+ var testCode = @"
+///
+/// This is an ordered list:
+/// <[|ul|]>
+///
+/// Item 1
+///
+///
+///
+class TestClass { }
+";
+ var fixedCode = @"
+///
+/// This is an ordered list:
+///
+/// -
+/// Item 1
+///
+///
+///
+class TestClass { }
+";
+
+ await Verify.VerifyCodeFixAsync(testCode, fixedCode);
+ }
+ }
+}
diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs b/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs
index 8e86abe..61177a3 100644
--- a/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs
+++ b/DocumentationAnalyzers/DocumentationAnalyzers/Helpers/XmlCommentHelper.cs
@@ -24,6 +24,9 @@ internal static class XmlCommentHelper
internal const string SeeXmlTag = "see";
internal const string CodeXmlTag = "code";
internal const string ListXmlTag = "list";
+ internal const string ItemXmlTag = "item";
+ internal const string TermXmlTag = "term";
+ internal const string DescriptionXmlTag = "description";
internal const string NoteXmlTag = "note";
internal const string ParaXmlTag = "para";
internal const string SeeAlsoXmlTag = "seealso";
@@ -40,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 TypeAttributeName = "type";
///
/// The <placeholder> tag is a Sandcastle Help File Builder extension to the standard XML documentation
diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/DOC103UseXmlDocumentationSyntax.cs b/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/DOC103UseXmlDocumentationSyntax.cs
new file mode 100644
index 0000000..ce68a53
--- /dev/null
+++ b/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/DOC103UseXmlDocumentationSyntax.cs
@@ -0,0 +1,68 @@
+// 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.StyleRules
+{
+ using System.Collections.Immutable;
+ using Microsoft.CodeAnalysis;
+ using Microsoft.CodeAnalysis.CSharp;
+ using Microsoft.CodeAnalysis.CSharp.Syntax;
+ using Microsoft.CodeAnalysis.Diagnostics;
+
+ ///
+ /// Use XML documentation syntax.
+ ///
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ internal class DOC103UseXmlDocumentationSyntax : DiagnosticAnalyzer
+ {
+ ///
+ /// The ID for diagnostics produced by the analyzer.
+ ///
+ public const string DiagnosticId = "DOC103";
+ private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC103.md";
+
+ private static readonly LocalizableString Title = new LocalizableResourceString(nameof(StyleResources.DOC103Title), StyleResources.ResourceManager, typeof(StyleResources));
+ private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(StyleResources.DOC103MessageFormat), StyleResources.ResourceManager, typeof(StyleResources));
+ private static readonly LocalizableString Description = new LocalizableResourceString(nameof(StyleResources.DOC103Description), StyleResources.ResourceManager, typeof(StyleResources));
+
+ private static readonly DiagnosticDescriptor Descriptor =
+ new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.StyleRules, DiagnosticSeverity.Info, 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(HandleXmlElementSyntax, SyntaxKind.XmlElement);
+ }
+
+ private static void HandleXmlElementSyntax(SyntaxNodeAnalysisContext context)
+ {
+ var xmlElementSyntax = (XmlElementSyntax)context.Node;
+ var name = xmlElementSyntax.StartTag?.Name;
+ if (name is null || name.Prefix != null)
+ {
+ return;
+ }
+
+ switch (name.LocalName.ValueText)
+ {
+ case "p":
+ case "pre":
+ case "tt":
+ case "ol":
+ case "ul":
+ break;
+
+ default:
+ return;
+ }
+
+ context.ReportDiagnostic(Diagnostic.Create(Descriptor, name.LocalName.GetLocation()));
+ }
+ }
+}
diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.Designer.cs b/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.Designer.cs
index 046a30c..fcca0d5 100644
--- a/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.Designer.cs
+++ b/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.Designer.cs
@@ -150,5 +150,32 @@ internal static string DOC102Title {
return ResourceManager.GetString("DOC102Title", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Use XML documentation syntax.
+ ///
+ internal static string DOC103Description {
+ get {
+ return ResourceManager.GetString("DOC103Description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use XML documentation syntax.
+ ///
+ internal static string DOC103MessageFormat {
+ get {
+ return ResourceManager.GetString("DOC103MessageFormat", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Use XML documentation syntax.
+ ///
+ internal static string DOC103Title {
+ get {
+ return ResourceManager.GetString("DOC103Title", resourceCulture);
+ }
+ }
}
}
diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.resx b/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.resx
index 2680f3e..9388bf7 100644
--- a/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.resx
+++ b/DocumentationAnalyzers/DocumentationAnalyzers/StyleRules/StyleResources.resx
@@ -147,4 +147,13 @@
Use child blocks consistently across elements of the same kind
+
+ Use XML documentation syntax
+
+
+ Use XML documentation syntax
+
+
+ Use XML documentation syntax
+
\ No newline at end of file
diff --git a/docs/DOC103.md b/docs/DOC103.md
new file mode 100644
index 0000000..7b50e2b
--- /dev/null
+++ b/docs/DOC103.md
@@ -0,0 +1,50 @@
+# DOC103
+
+
+
+ TypeName |
+ DOC103UseXmlDocumentationSyntax |
+
+
+ CheckId |
+ DOC103 |
+
+
+ Category |
+ Style Rules |
+
+
+
+## Cause
+
+The documentation for the element an HTML element equivalent to a known XML documentation element.
+
+## Rule description
+
+A violation of this rule occurs when an XML documentation comment contains an HTML element instead of the corresponding
+XML documentation comment syntax.
+
+## How to fix violations
+
+To fix a violation of this rule, use the expected XML documentation element instead of the HTML element.
+
+| HTML Element | XML Element |
+| --- | --- |
+| `` | `` |
+| `` | `` |
+| `` | `` |
+| `` | `` |
+| `` | `` |
+
+## How to suppress violations
+
+```csharp
+#pragma warning disable DOC103 // Use XML documentation syntax
+///
+/// Summary text with inline code.
+///
+public void SomeOperation()
+#pragma warning restore DOC103 // Use XML documentation syntax
+{
+}
+```
diff --git a/docs/StyleRules.md b/docs/StyleRules.md
index ca7cbd7..22e3c52 100644
--- a/docs/StyleRules.md
+++ b/docs/StyleRules.md
@@ -7,3 +7,4 @@ Identifier | Name | Description
[DOC100](DOC100.md) | PlaceTextInParagraphs | A `` or `` documentation element contains content which is not wrapped in a block-level element.
[DOC101](DOC101.md) | UseChildBlocksConsistently | The documentation for the element contains some text which is wrapped in block-level elements, and other text which is written inline.
[DOC102](DOC102.md) | UseChildBlocksConsistentlyAcrossElementsOfTheSameKind | The documentation for the element contains inline text, but the documentation for a sibling element of the same kind uses block-level elements.
+[DOC103](DOC103.md) | UseXmlDocumentationSyntax | The documentation for the element an HTML element equivalent to a known XML documentation element.