From 125ee0a171f5bfe15a1cf8d5d2d619bfa4bb58f2 Mon Sep 17 00:00:00 2001 From: Sam Harwell Date: Wed, 19 Sep 2018 11:56:17 -0500 Subject: [PATCH] Implement element usage analyzers Add analyzers and fixes for incorrect placement of section, block, and/or inline elements. Closes #30 Closes #33 Closes #34 --- .../PortabilityRules/DOC202CodeFixProvider.cs | 84 ++++++ .../PortabilityRules/DOC203CodeFixProvider.cs | 80 +++++ .../PortabilityRules/DOC204CodeFixProvider.cs | 84 ++++++ .../DOC202CSharp7UnitTests.cs | 11 + .../DOC203CSharp7UnitTests.cs | 11 + .../DOC204CSharp7UnitTests.cs | 11 + .../PortabilityRules/DOC202UnitTests.cs | 90 ++++++ .../PortabilityRules/DOC203UnitTests.cs | 90 ++++++ .../PortabilityRules/DOC204UnitTests.cs | 92 ++++++ .../DOC202UseSectionElementsCorrectly.cs | 70 +++++ .../DOC203UseBlockElementsCorrectly.cs | 283 ++++++++++++++++++ .../DOC204UseInlineElementsCorrectly.cs | 70 +++++ .../PortabilityResources.Designer.cs | 108 +++++++ .../PortabilityResources.resx | 36 +++ docs/DOC202.md | 32 ++ docs/DOC203.md | 32 ++ docs/DOC204.md | 32 ++ docs/PortabilityRules.md | 3 + 18 files changed, 1219 insertions(+) create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC202CodeFixProvider.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC203CodeFixProvider.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC204CodeFixProvider.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC202CSharp7UnitTests.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC203CSharp7UnitTests.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC204CSharp7UnitTests.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC202UnitTests.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC203UnitTests.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC204UnitTests.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC202UseSectionElementsCorrectly.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC203UseBlockElementsCorrectly.cs create mode 100644 DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC204UseInlineElementsCorrectly.cs create mode 100644 docs/DOC202.md create mode 100644 docs/DOC203.md create mode 100644 docs/DOC204.md diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC202CodeFixProvider.cs b/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC202CodeFixProvider.cs new file mode 100644 index 0000000..c9473a8 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC202CodeFixProvider.cs @@ -0,0 +1,84 @@ +// 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.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(DOC202CodeFixProvider))] + [Shared] + internal class DOC202CodeFixProvider : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } + = ImmutableArray.Create(DOC202UseSectionElementsCorrectly.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( + PortabilityResources.DOC202CodeFix, + token => GetTransformedDocumentAsync(context.Document, diagnostic, token), + nameof(DOC202CodeFixProvider)), + 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 xmlNode = token.Parent.FirstAncestorOrSelf(); + var oldStartToken = xmlNode.GetName().LocalName; + + string newIdentifier; + switch (oldStartToken.ValueText) + { + case XmlCommentHelper.ParamXmlTag: + newIdentifier = XmlCommentHelper.ParamRefXmlTag; + break; + + case XmlCommentHelper.TypeParamXmlTag: + newIdentifier = XmlCommentHelper.TypeParamRefXmlTag; + break; + + default: + // Not handled + return document; + } + + var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, newIdentifier, oldStartToken.TrailingTrivia); + var newXmlNode = xmlNode.ReplaceToken(oldStartToken, newStartToken); + + if (newXmlNode is XmlElementSyntax newXmlElement) + { + var oldEndToken = newXmlElement.EndTag.Name.LocalName; + var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, newIdentifier, oldEndToken.TrailingTrivia); + newXmlNode = newXmlNode.ReplaceToken(oldEndToken, newEndToken); + } + + return document.WithSyntaxRoot(root.ReplaceNode(xmlNode, newXmlNode)); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC203CodeFixProvider.cs b/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC203CodeFixProvider.cs new file mode 100644 index 0000000..7269a3b --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC203CodeFixProvider.cs @@ -0,0 +1,80 @@ +// 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.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(DOC203CodeFixProvider))] + [Shared] + internal class DOC203CodeFixProvider : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } + = ImmutableArray.Create(DOC203UseBlockElementsCorrectly.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( + PortabilityResources.DOC203CodeFix, + token => GetTransformedDocumentAsync(context.Document, diagnostic, token), + nameof(DOC203CodeFixProvider)), + 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 xmlNode = token.Parent.FirstAncestorOrSelf(); + var oldStartToken = xmlNode.GetName().LocalName; + + string newIdentifier; + switch (oldStartToken.ValueText) + { + case XmlCommentHelper.CodeXmlTag: + newIdentifier = XmlCommentHelper.CXmlTag; + break; + + default: + // Not handled + return document; + } + + var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, newIdentifier, oldStartToken.TrailingTrivia); + var newXmlNode = xmlNode.ReplaceToken(oldStartToken, newStartToken); + + if (newXmlNode is XmlElementSyntax newXmlElement) + { + var oldEndToken = newXmlElement.EndTag.Name.LocalName; + var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, newIdentifier, oldEndToken.TrailingTrivia); + newXmlNode = newXmlNode.ReplaceToken(oldEndToken, newEndToken); + } + + return document.WithSyntaxRoot(root.ReplaceNode(xmlNode, newXmlNode)); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC204CodeFixProvider.cs b/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC204CodeFixProvider.cs new file mode 100644 index 0000000..8165429 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.CodeFixes/PortabilityRules/DOC204CodeFixProvider.cs @@ -0,0 +1,84 @@ +// 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.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(DOC204CodeFixProvider))] + [Shared] + internal class DOC204CodeFixProvider : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds { get; } + = ImmutableArray.Create(DOC204UseInlineElementsCorrectly.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( + PortabilityResources.DOC204CodeFix, + token => GetTransformedDocumentAsync(context.Document, diagnostic, token), + nameof(DOC204CodeFixProvider)), + 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 xmlNode = token.Parent.FirstAncestorOrSelf(); + var oldStartToken = xmlNode.GetName().LocalName; + + string newIdentifier; + switch (oldStartToken.ValueText) + { + case XmlCommentHelper.ParamRefXmlTag: + newIdentifier = XmlCommentHelper.ParamXmlTag; + break; + + case XmlCommentHelper.TypeParamRefXmlTag: + newIdentifier = XmlCommentHelper.TypeParamXmlTag; + break; + + default: + // Not handled + return document; + } + + var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, newIdentifier, oldStartToken.TrailingTrivia); + var newXmlNode = xmlNode.ReplaceToken(oldStartToken, newStartToken); + + if (newXmlNode is XmlElementSyntax newXmlElement) + { + var oldEndToken = newXmlElement.EndTag.Name.LocalName; + var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, newIdentifier, oldEndToken.TrailingTrivia); + newXmlNode = newXmlNode.ReplaceToken(oldEndToken, newEndToken); + } + + return document.WithSyntaxRoot(root.ReplaceNode(xmlNode, newXmlNode)); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC202CSharp7UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC202CSharp7UnitTests.cs new file mode 100644 index 0000000..81b96f2 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC202CSharp7UnitTests.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 DOC202CSharp7UnitTests : DOC202UnitTests + { + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC203CSharp7UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC203CSharp7UnitTests.cs new file mode 100644 index 0000000..9d7f8ac --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC203CSharp7UnitTests.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 DOC203CSharp7UnitTests : DOC203UnitTests + { + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC204CSharp7UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC204CSharp7UnitTests.cs new file mode 100644 index 0000000..dee7758 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test.CSharp7/PortabilityRules/DOC204CSharp7UnitTests.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 DOC204CSharp7UnitTests : DOC204UnitTests + { + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC202UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC202UnitTests.cs new file mode 100644 index 0000000..7b61021 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC202UnitTests.cs @@ -0,0 +1,90 @@ +// 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 DOC202UnitTests + { + [Fact] + public async Task TestElementsUsedCorrectlyAsync() + { + var testCode = @" +class TestClass +{ + /// + /// Pass in a value. + /// + /// The type of value + /// The value + void Method(int value) + { + } +} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task TestParamUsedAsParamRefAsync() + { + var testCode = @" +class TestClass +{ + /// + /// Pass in a <$$param name=""value""/>. + /// + void Method(int value) + { + } +} +"; + var fixedCode = @" +class TestClass +{ + /// + /// Pass in a . + /// + void Method(int value) + { + } +} +"; + + await Verify.VerifyCodeFixAsync(testCode, fixedCode); + } + + [Fact] + public async Task TestTypeParamUsedAsTypeParamRefAsync() + { + var testCode = @" +class TestClass +{ + /// + /// Pass in a <$$typeparam name=""T""/>. + /// + void Method() + { + } +} +"; + var fixedCode = @" +class TestClass +{ + /// + /// Pass in a . + /// + void Method() + { + } +} +"; + + await Verify.VerifyCodeFixAsync(testCode, fixedCode); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC203UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC203UnitTests.cs new file mode 100644 index 0000000..6ea2ee9 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC203UnitTests.cs @@ -0,0 +1,90 @@ +// 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 DOC203UnitTests + { + [Fact] + public async Task TestCodeUsedAsExampleAsync() + { + var testCode = @" +class TestClass +{ + /// + /// + /// this is some code... + /// + /// + void Method() + { + } +} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task TestCodeUsedAsInlineWithinParagraphAsync() + { + var testCode = @" +class TestClass +{ + /// + /// Executes some <$$code>code. + /// + void Method() + { + } +} +"; + var fixedCode = @" +class TestClass +{ + /// + /// Executes some code. + /// + void Method() + { + } +} +"; + + await Verify.VerifyCodeFixAsync(testCode, fixedCode); + } + + [Fact] + public async Task TestCodeUsedAsInlineAsync() + { + var testCode = @" +class TestClass +{ + /// + /// Executes some <$$code>code. + /// + void Method() + { + } +} +"; + var fixedCode = @" +class TestClass +{ + /// + /// Executes some code. + /// + void Method() + { + } +} +"; + + await Verify.VerifyCodeFixAsync(testCode, fixedCode); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC204UnitTests.cs b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC204UnitTests.cs new file mode 100644 index 0000000..5a0f671 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers.Test/PortabilityRules/DOC204UnitTests.cs @@ -0,0 +1,92 @@ +// 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 DOC204UnitTests + { + [Fact] + public async Task TestElementsUsedCorrectlyAsync() + { + var testCode = @" +class TestClass +{ + /// + /// Pass in a of type . + /// + void Method(int value) + { + } +} +"; + + await Verify.VerifyAnalyzerAsync(testCode); + } + + [Fact] + public async Task TestParamRefUsedAsParamAsync() + { + var testCode = @" +class TestClass +{ + /// + /// Pass in a value. + /// + /// <$$paramref name=""value"">The value + void Method(int value) + { + } +} +"; + var fixedCode = @" +class TestClass +{ + /// + /// Pass in a value. + /// + /// The value + void Method(int value) + { + } +} +"; + + await Verify.VerifyCodeFixAsync(testCode, fixedCode); + } + + [Fact] + public async Task TestTypeParamUsedAsTypeParamRefAsync() + { + var testCode = @" +class TestClass +{ + /// + /// Pass in a value. + /// + /// <$$typeparamref name=""T"">The type of value + void Method() + { + } +} +"; + var fixedCode = @" +class TestClass +{ + /// + /// Pass in a value. + /// + /// The type of value + void Method() + { + } +} +"; + + await Verify.VerifyCodeFixAsync(testCode, fixedCode); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC202UseSectionElementsCorrectly.cs b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC202UseSectionElementsCorrectly.cs new file mode 100644 index 0000000..8b23cd3 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC202UseSectionElementsCorrectly.cs @@ -0,0 +1,70 @@ +// 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 DocumentationAnalyzers.Helpers; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class DOC202UseSectionElementsCorrectly : DiagnosticAnalyzer + { + /// + /// The ID for diagnostics produced by the analyzer. + /// + public const string DiagnosticId = "DOC202"; + private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC202.md"; + + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(PortabilityResources.DOC202Title), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(PortabilityResources.DOC202MessageFormat), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + private static readonly LocalizableString Description = new LocalizableResourceString(nameof(PortabilityResources.DOC202Description), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.PortabilityRules, 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(HandleXmlNodeSyntax, SyntaxKind.XmlElement, SyntaxKind.XmlEmptyElement); + } + + private static void HandleXmlNodeSyntax(SyntaxNodeAnalysisContext context) + { + var xmlNodeSyntax = (XmlNodeSyntax)context.Node; + if (xmlNodeSyntax.Parent.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) + || xmlNodeSyntax.Parent.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia)) + { + // Element is used as a section element + return; + } + + var name = xmlNodeSyntax.GetName(); + if (name is null || name.Prefix != null) + { + return; + } + + switch (name.LocalName.ValueText) + { + case XmlCommentHelper.ParamXmlTag: + case XmlCommentHelper.TypeParamXmlTag: + break; + + default: + return; + } + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, name.LocalName.GetLocation())); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC203UseBlockElementsCorrectly.cs b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC203UseBlockElementsCorrectly.cs new file mode 100644 index 0000000..cce2c9d --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC203UseBlockElementsCorrectly.cs @@ -0,0 +1,283 @@ +// 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.Diagnostics; + using DocumentationAnalyzers.Helpers; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class DOC203UseBlockElementsCorrectly : DiagnosticAnalyzer + { + /// + /// The ID for diagnostics produced by the analyzer. + /// + public const string DiagnosticId = "DOC203"; + private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC203.md"; + + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(PortabilityResources.DOC203Title), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(PortabilityResources.DOC203MessageFormat), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + private static readonly LocalizableString Description = new LocalizableResourceString(nameof(PortabilityResources.DOC203Description), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.PortabilityRules, 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(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; + } + + switch (name.LocalName.ValueText) + { + case XmlCommentHelper.CodeXmlTag: + break; + + default: + return; + } + + if (!RequiresSectionContent(xmlNodeSyntax.Parent) && !RequiresInlineContent(xmlNodeSyntax.Parent)) + { + // The parent may or may not allow block content here. For now, assume that the element is treated as a + // proper block if the start element is the first on the line, and the end element is the last on its + // line. + if (xmlNodeSyntax is XmlElementSyntax xmlElement) + { + if (IsFirstOnLine(xmlElement.StartTag) && IsLastOnLine(xmlElement.EndTag)) + { + return; + } + } + else + { + if (IsFirstOnLine(xmlNodeSyntax) && IsLastOnLine(xmlNodeSyntax)) + { + return; + } + } + } + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, name.LocalName.GetLocation())); + } + + private static bool IsFirstOnLine(XmlNodeSyntax xmlNode) + { + // Need to examine the preceding sibling + SyntaxList parentContent; + if (xmlNode.Parent is DocumentationCommentTriviaSyntax documentationCommentTrivia) + { + parentContent = documentationCommentTrivia.Content; + } + else if (xmlNode.Parent is XmlElementSyntax xmlElement) + { + parentContent = xmlElement.Content; + } + else + { + return false; + } + + for (int i = parentContent.IndexOf(xmlNode) - 1; i >= 0; i--) + { + if (!(parentContent[i] is XmlTextSyntax xmlText)) + { + return false; + } + + for (int j = xmlText.TextTokens.Count - 1; j >= 0; j--) + { + if (xmlText.TextTokens[j].IsKind(SyntaxKind.XmlTextLiteralNewLineToken)) + { + return true; + } + else if (!string.IsNullOrWhiteSpace(xmlText.TextTokens[j].ValueText)) + { + return false; + } + } + } + + return false; + } + + private static bool IsFirstOnLine(XmlElementStartTagSyntax xmlElementStartTag) + { + // Need to examine the preceding sibling of the parent + return xmlElementStartTag.Parent is XmlElementSyntax xmlElement + && IsFirstOnLine(xmlElement); + } + + private static bool IsFirstOnLine(XmlElementEndTagSyntax xmlElementEndTag) + { + // Need to examine the last content text + SyntaxList parentContent; + if (xmlElementEndTag.Parent is XmlElementSyntax xmlElement) + { + parentContent = xmlElement.Content; + } + else + { + return false; + } + + for (int i = parentContent.Count - 1; i >= 0; i--) + { + if (!(parentContent[i] is XmlTextSyntax xmlText)) + { + return false; + } + + for (int j = xmlText.TextTokens.Count - 1; j >= 0; j--) + { + if (xmlText.TextTokens[j].IsKind(SyntaxKind.XmlTextLiteralNewLineToken)) + { + return true; + } + else if (!string.IsNullOrWhiteSpace(xmlText.TextTokens[j].ValueText)) + { + return false; + } + } + } + + return false; + } + + private static bool IsLastOnLine(XmlNodeSyntax xmlNode) + { + // Need to examine the following sibling + SyntaxList parentContent; + if (xmlNode.Parent is DocumentationCommentTriviaSyntax documentationCommentTrivia) + { + parentContent = documentationCommentTrivia.Content; + } + else if (xmlNode.Parent is XmlElementSyntax xmlElement) + { + parentContent = xmlElement.Content; + } + else + { + return false; + } + + for (int i = parentContent.IndexOf(xmlNode) + 1; i < parentContent.Count; i++) + { + if (!(parentContent[i] is XmlTextSyntax xmlText)) + { + return false; + } + + for (int j = 0; j < xmlText.TextTokens.Count; j++) + { + if (xmlText.TextTokens[j].IsKind(SyntaxKind.XmlTextLiteralNewLineToken)) + { + return true; + } + else if (!string.IsNullOrWhiteSpace(xmlText.TextTokens[j].ValueText)) + { + return false; + } + } + } + + return false; + } + + private static bool IsLastOnLine(XmlElementStartTagSyntax xmlElementStartTag) + { + // Need to examine the first content text + SyntaxList parentContent; + if (xmlElementStartTag.Parent is XmlElementSyntax xmlElement) + { + parentContent = xmlElement.Content; + } + else + { + return false; + } + + for (int i = 0; i < parentContent.Count; i++) + { + if (!(parentContent[i] is XmlTextSyntax xmlText)) + { + return false; + } + + for (int j = 0; j < xmlText.TextTokens.Count; j++) + { + if (xmlText.TextTokens[j].IsKind(SyntaxKind.XmlTextLiteralNewLineToken)) + { + return true; + } + else if (!string.IsNullOrWhiteSpace(xmlText.TextTokens[j].ValueText)) + { + return false; + } + } + } + + return false; + } + + private static bool IsLastOnLine(XmlElementEndTagSyntax xmlElementEndTag) + { + // Need to examine the following sibling of the parent + return xmlElementEndTag.Parent is XmlElementSyntax xmlElement + && IsLastOnLine(xmlElement); + } + + private static bool RequiresSectionContent(SyntaxNode parent) + { + return parent.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) + || parent.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia); + } + + private static bool RequiresInlineContent(SyntaxNode parent) + { + Debug.Assert(!RequiresSectionContent(parent), "Assertion failed: !RequiresSectionContent(parent)"); + + if (!(parent is XmlNodeSyntax xmlNode)) + { + // Unrecognized parent element kind => unknown content kind + return false; + } + + var name = xmlNode.GetName(); + if (name is null || name.Prefix != null) + { + return false; + } + + switch (name.LocalName.ValueText) + { + case XmlCommentHelper.ParaXmlTag: + return true; + + default: + return false; + } + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC204UseInlineElementsCorrectly.cs b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC204UseInlineElementsCorrectly.cs new file mode 100644 index 0000000..be5d5b1 --- /dev/null +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/DOC204UseInlineElementsCorrectly.cs @@ -0,0 +1,70 @@ +// 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 DocumentationAnalyzers.Helpers; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Diagnostics; + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class DOC204UseInlineElementsCorrectly : DiagnosticAnalyzer + { + /// + /// The ID for diagnostics produced by the analyzer. + /// + public const string DiagnosticId = "DOC204"; + private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC204.md"; + + private static readonly LocalizableString Title = new LocalizableResourceString(nameof(PortabilityResources.DOC204Title), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(PortabilityResources.DOC204MessageFormat), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + private static readonly LocalizableString Description = new LocalizableResourceString(nameof(PortabilityResources.DOC204Description), PortabilityResources.ResourceManager, typeof(PortabilityResources)); + + private static readonly DiagnosticDescriptor Descriptor = + new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.PortabilityRules, 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(HandleXmlNodeSyntax, SyntaxKind.XmlElement, SyntaxKind.XmlEmptyElement); + } + + private static void HandleXmlNodeSyntax(SyntaxNodeAnalysisContext context) + { + var xmlNodeSyntax = (XmlNodeSyntax)context.Node; + if (!xmlNodeSyntax.Parent.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia) + && !xmlNodeSyntax.Parent.IsKind(SyntaxKind.MultiLineDocumentationCommentTrivia)) + { + // Element is not used as a section element + return; + } + + var name = xmlNodeSyntax.GetName(); + if (name is null || name.Prefix != null) + { + return; + } + + switch (name.LocalName.ValueText) + { + case XmlCommentHelper.ParamRefXmlTag: + case XmlCommentHelper.TypeParamRefXmlTag: + break; + + default: + return; + } + + context.ReportDiagnostic(Diagnostic.Create(Descriptor, name.LocalName.GetLocation())); + } + } +} diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs index 2d4dae3..dd8bd35 100644 --- a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.Designer.cs @@ -132,5 +132,113 @@ internal static string DOC201Title { return ResourceManager.GetString("DOC201Title", resourceCulture); } } + + /// + /// Looks up a localized string similar to Convert to expected element. + /// + internal static string DOC202CodeFix { + get { + return ResourceManager.GetString("DOC202CodeFix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use section elements correctly. + /// + internal static string DOC202Description { + get { + return ResourceManager.GetString("DOC202Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use section elements correctly. + /// + internal static string DOC202MessageFormat { + get { + return ResourceManager.GetString("DOC202MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use section elements correctly. + /// + internal static string DOC202Title { + get { + return ResourceManager.GetString("DOC202Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Convert to expected element. + /// + internal static string DOC203CodeFix { + get { + return ResourceManager.GetString("DOC203CodeFix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use block elements correctly. + /// + internal static string DOC203Description { + get { + return ResourceManager.GetString("DOC203Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use block elements correctly. + /// + internal static string DOC203MessageFormat { + get { + return ResourceManager.GetString("DOC203MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use block elements correctly. + /// + internal static string DOC203Title { + get { + return ResourceManager.GetString("DOC203Title", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Convert to expected element. + /// + internal static string DOC204CodeFix { + get { + return ResourceManager.GetString("DOC204CodeFix", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use inline elements correctly. + /// + internal static string DOC204Description { + get { + return ResourceManager.GetString("DOC204Description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use inline elements correctly. + /// + internal static string DOC204MessageFormat { + get { + return ResourceManager.GetString("DOC204MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use inline elements correctly. + /// + internal static string DOC204Title { + get { + return ResourceManager.GetString("DOC204Title", resourceCulture); + } + } } } diff --git a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx index c33f490..a2bd94b 100644 --- a/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx +++ b/DocumentationAnalyzers/DocumentationAnalyzers/PortabilityRules/PortabilityResources.resx @@ -141,4 +141,40 @@ Item should have description + + Convert to expected element + + + Use section elements correctly + + + Use section elements correctly + + + Use section elements correctly + + + Convert to expected element + + + Use block elements correctly + + + Use block elements correctly + + + Use block elements correctly + + + Convert to expected element + + + Use inline elements correctly + + + Use inline elements correctly + + + Use inline elements correctly + \ No newline at end of file diff --git a/docs/DOC202.md b/docs/DOC202.md new file mode 100644 index 0000000..6edc542 --- /dev/null +++ b/docs/DOC202.md @@ -0,0 +1,32 @@ +# DOC202 + + + + + + + + + + + + + + +
TypeNameDOC202UseSectionElementsCorrectly
CheckIdDOC202
CategoryPortability Rules
+ +## Cause + +The documentation contains a section element where a block or inline element was expected. + +## Rule description + +*TODO* + +## How to fix violations + +*TODO* + +## How to suppress violations + +*TODO* diff --git a/docs/DOC203.md b/docs/DOC203.md new file mode 100644 index 0000000..b61ab52 --- /dev/null +++ b/docs/DOC203.md @@ -0,0 +1,32 @@ +# DOC203 + + + + + + + + + + + + + + +
TypeNameDOC203UseBlockElementsCorrectly
CheckIdDOC203
CategoryPortability Rules
+ +## Cause + +The documentation contains a block element where a section or inline element was expected. + +## Rule description + +*TODO* + +## How to fix violations + +*TODO* + +## How to suppress violations + +*TODO* diff --git a/docs/DOC204.md b/docs/DOC204.md new file mode 100644 index 0000000..fcaee89 --- /dev/null +++ b/docs/DOC204.md @@ -0,0 +1,32 @@ +# DOC204 + + + + + + + + + + + + + + +
TypeNameDOC204UseInlineElementsCorrectly
CheckIdDOC204
CategoryPortability Rules
+ +## Cause + +The documentation contains an inline element where a section or block element was expected. + +## Rule description + +*TODO* + +## How to fix violations + +*TODO* + +## How to suppress violations + +*TODO* diff --git a/docs/PortabilityRules.md b/docs/PortabilityRules.md index 7369d18..914d73e 100644 --- a/docs/PortabilityRules.md +++ b/docs/PortabilityRules.md @@ -6,3 +6,6 @@ Identifier | Name | Description -----------|------|------------- [DOC200](DOC200.md) | UseXmlDocumentationSyntax | The documentation for the element an HTML element equivalent to a known XML documentation element. [DOC201](DOC201.md) | ItemShouldHaveDescription | The documentation for an `` within a `` did not include the required `` and/or `` elements. +[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.