From 022d78df44cb8a11400abf082a25f274fa1ed4da Mon Sep 17 00:00:00 2001 From: Carsten Scholling Date: Wed, 17 Jun 2026 08:37:43 +0200 Subject: [PATCH 1/4] #341 DC0006 ProcedureRequiresDocumentation (Internal) --- .../CodeunitAccessInternal.al | 0 ...nitAccessInternalProcedureWithAttribute.al | 9 +++ ...eunitAccessInternalProcedureWithComment.al | 9 +++ .../InternalProcedure.al} | 0 .../InternalProcedureWithAttribute.al | 7 ++ .../InternalProcedureWithComment.al | 7 ++ ...ssInternalProcedureDocumentationComment.al | 12 +++ ...cedureDocumentationCommentWithAttribute.al | 13 +++ ...umentationCommentWithMultipleAttributes.al | 13 +++ .../Procedure.al | 0 .../ProcedureLocal.al | 0 .../TestCodeunit.al | 0 .../TestCodeunitHandlerMethod.al | 0 .../ProcedureRequiresDocumentation.cs | 81 +++++++++++++++++++ .../PublicHasDiagnostic/Procedure.al | 6 ++ .../ProcedureWithAttribute.al | 0 .../ProcedureWithComment.al | 0 .../CodeunitAccessInternal.al | 8 ++ .../ProcedureDocumentationComment.al | 0 ...cedureDocumentationCommentWithAttribute.al | 0 ...umentationCommentWithMultipleAttributes.al | 0 .../PublicNoDiagnostic/ProcedureInternal.al | 6 ++ .../PublicNoDiagnostic/ProcedureLocal.al | 6 ++ .../PublicNoDiagnostic/TestCodeunit.al | 9 +++ .../TestCodeunitHandlerMethod.al | 19 +++++ .../PublicProcedureRequiresDocumentation.cs | 50 ------------ .../ALCops.DocumentationCopAnalyzers.resx | 9 +++ .../ProcedureRequiresDocumentation.cs | 73 +++++++++++++++++ .../PublicProcedureRequiresDocumentation.cs | 65 --------------- .../DiagnosticDescriptors.cs | 10 +++ src/ALCops.DocumentationCop/DiagnosticIds.cs | 1 + 31 files changed, 298 insertions(+), 115 deletions(-) rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{NoDiagnostic => InternalHasDiagnostic}/CodeunitAccessInternal.al (100%) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{NoDiagnostic/ProcedureInternal.al => InternalHasDiagnostic/InternalProcedure.al} (100%) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithAttribute.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithComment.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{HasDiagnostic => InternalNoDiagnostic}/Procedure.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{NoDiagnostic => InternalNoDiagnostic}/ProcedureLocal.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{NoDiagnostic => InternalNoDiagnostic}/TestCodeunit.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{NoDiagnostic => InternalNoDiagnostic}/TestCodeunitHandlerMethod.al (100%) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/Procedure.al rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{HasDiagnostic => PublicHasDiagnostic}/ProcedureWithAttribute.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{HasDiagnostic => PublicHasDiagnostic}/ProcedureWithComment.al (100%) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/CodeunitAccessInternal.al rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{NoDiagnostic => PublicNoDiagnostic}/ProcedureDocumentationComment.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{NoDiagnostic => PublicNoDiagnostic}/ProcedureDocumentationCommentWithAttribute.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/{NoDiagnostic => PublicNoDiagnostic}/ProcedureDocumentationCommentWithMultipleAttributes.al (100%) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureInternal.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureLocal.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunit.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunitHandlerMethod.al delete mode 100644 src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicProcedureRequiresDocumentation.cs create mode 100644 src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs delete mode 100644 src/ALCops.DocumentationCop/Analyzers/PublicProcedureRequiresDocumentation.cs diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/CodeunitAccessInternal.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternal.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/CodeunitAccessInternal.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternal.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al new file mode 100644 index 00000000..9f60c304 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al @@ -0,0 +1,9 @@ +codeunit 50100 MyCodeunit +{ + Access = Internal; + + [NonDebuggable] + procedure [|MyProcedure|]() + begin + end; +} diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al new file mode 100644 index 00000000..e31cf541 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al @@ -0,0 +1,9 @@ +codeunit 50100 MyCodeunit +{ + Access = Internal; + + // This is a comment + procedure [|MyProcedure|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureInternal.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedure.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureInternal.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedure.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithAttribute.al new file mode 100644 index 00000000..874cf0f8 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithAttribute.al @@ -0,0 +1,7 @@ +codeunit 50100 MyCodeunit +{ + [NonDebuggable] + internal procedure [|MyProcedure|]() + begin + end; +} diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithComment.al new file mode 100644 index 00000000..762d6585 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithComment.al @@ -0,0 +1,7 @@ +codeunit 50100 MyCodeunit +{ + // This is a comment + internal procedure [|MyProcedure|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al new file mode 100644 index 00000000..e2967afe --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al @@ -0,0 +1,12 @@ +codeunit 50100 MyCodeunit +{ + Access = Internal; + + /// + /// This method... + /// + procedure [|MyProcedure|]() + begin + + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al new file mode 100644 index 00000000..16e28a71 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al @@ -0,0 +1,13 @@ +codeunit 50100 MyCodeunit +{ + Access = Internal; + + /// + /// Sets the call body content from a stream. + /// + /// The instream containing the call body. + [NonDebuggable] + procedure [|MyProcedure|](ContentStream: InStream) + begin + end; +} diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al new file mode 100644 index 00000000..ea490a6a --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al @@ -0,0 +1,13 @@ +codeunit 50100 MyCodeunit +{ + Access = Internal; + + /// + /// My procedure. + /// + [NonDebuggable] + [Scope('OnPrem')] + procedure [|MyProcedure|]() + begin + end; +} diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/HasDiagnostic/Procedure.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/Procedure.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/HasDiagnostic/Procedure.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/Procedure.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureLocal.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/ProcedureLocal.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureLocal.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/ProcedureLocal.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/TestCodeunit.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunit.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/TestCodeunit.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunit.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/TestCodeunitHandlerMethod.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunitHandlerMethod.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/TestCodeunitHandlerMethod.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunitHandlerMethod.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs new file mode 100644 index 00000000..aae7185b --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs @@ -0,0 +1,81 @@ +using RoslynTestKit; + +namespace ALCops.DocumentationCop.Test +{ + public class PublicProcedureRequiresDocumentation : NavCodeAnalysisBase + { + private AnalyzerTestFixture _fixture; + private string _testCasePath; + + [SetUp] + public void Setup() + { + _fixture = RoslynFixtureFactory.Create(); + + _testCasePath = Path.Combine( + Directory.GetParent( + Environment.CurrentDirectory)!.Parent!.Parent!.FullName, + Path.Combine("Rules", nameof(PublicProcedureRequiresDocumentation))); + } + + [Test] + [TestCase("Procedure")] + [TestCase("ProcedureWithAttribute")] + [TestCase("ProcedureWithComment")] + public async Task PublicHasDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(PublicHasDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.PublicProcedureRequiresDocumentation); + } + + [Test] + [TestCase("CodeunitAccessInternal")] + [TestCase("ProcedureDocumentationComment")] + [TestCase("ProcedureDocumentationCommentWithAttribute")] + [TestCase("ProcedureDocumentationCommentWithMultipleAttributes")] + [TestCase("ProcedureInternal")] + [TestCase("ProcedureLocal")] + [TestCase("TestCodeunit")] + [TestCase("TestCodeunitHandlerMethod")] + public async Task PublicNoDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(PublicNoDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.PublicProcedureRequiresDocumentation); + } + + [Test] + [TestCase("CodeunitAccessInternal")] + [TestCase("CodeunitAccessInternalProcedureWithAttribute")] + [TestCase("CodeunitAccessInternalProcedureWithComment")] + [TestCase("InternalProcedure")] + [TestCase("InternalProcedureWithAttribute")] + [TestCase("InternalProcedureWithComment")] + public async Task InternalHasDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(InternalHasDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.InternalProcedureRequiresDocumentation); + } + + [Test] + [TestCase("CodeunitAccessInternalProcedureDocumentationComment")] + [TestCase("CodeunitAccessInternalProcedureDocumentationCommentWithAttribute")] + [TestCase("CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes")] + [TestCase("Procedure")] + [TestCase("ProcedureLocal")] + [TestCase("TestCodeunit")] + [TestCase("TestCodeunitHandlerMethod")] + public async Task InternalNoDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(InternalNoDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.InternalProcedureRequiresDocumentation); + } + } +} diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/Procedure.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/Procedure.al new file mode 100644 index 00000000..ffd4a138 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/Procedure.al @@ -0,0 +1,6 @@ +codeunit 50100 MyCodeunit +{ + procedure [|MyProcedure|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/HasDiagnostic/ProcedureWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithAttribute.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/HasDiagnostic/ProcedureWithAttribute.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithAttribute.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/HasDiagnostic/ProcedureWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithComment.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/HasDiagnostic/ProcedureWithComment.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithComment.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/CodeunitAccessInternal.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/CodeunitAccessInternal.al new file mode 100644 index 00000000..720b3f9b --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/CodeunitAccessInternal.al @@ -0,0 +1,8 @@ +codeunit 50100 MyCodeunit +{ + Access = Internal; + + procedure [|MyProcedure|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureDocumentationComment.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationComment.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureDocumentationComment.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationComment.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureDocumentationCommentWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithAttribute.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureDocumentationCommentWithAttribute.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithAttribute.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/NoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al rename to src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureInternal.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureInternal.al new file mode 100644 index 00000000..0bfd9ef6 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureInternal.al @@ -0,0 +1,6 @@ +codeunit 50100 MyCodeunit +{ + internal procedure [|MyProcedure|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureLocal.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureLocal.al new file mode 100644 index 00000000..3bc4dd3e --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureLocal.al @@ -0,0 +1,6 @@ +codeunit 50100 MyCodeunit +{ + local procedure [|MyProcedure|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunit.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunit.al new file mode 100644 index 00000000..8a20c657 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunit.al @@ -0,0 +1,9 @@ +codeunit 50100 MyTestCodeunit +{ + Subtype = Test; + + [Test] + procedure [|MyTestProcedure|]() + begin + end; +} diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunitHandlerMethod.al b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunitHandlerMethod.al new file mode 100644 index 00000000..47c3f727 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunitHandlerMethod.al @@ -0,0 +1,19 @@ +codeunit 50100 MyTestCodeunit +{ + Subtype = Test; + + [Test] + procedure [|MyTestProcedure|]() + begin + end; + + [MessageHandler] + procedure [|MyMessageHandler|](Message: Text[1024]) + begin + end; + + [ConfirmHandler] + procedure [|MyConfirmHandler|](Question: Text[1024]; var Reply: Boolean) + begin + end; +} diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicProcedureRequiresDocumentation.cs deleted file mode 100644 index c75ae8d5..00000000 --- a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicProcedureRequiresDocumentation.cs +++ /dev/null @@ -1,50 +0,0 @@ -using RoslynTestKit; - -namespace ALCops.DocumentationCop.Test -{ - public class PublicProcedureRequiresDocumentation : NavCodeAnalysisBase - { - private AnalyzerTestFixture _fixture; - private string _testCasePath; - - [SetUp] - public void Setup() - { - _fixture = RoslynFixtureFactory.Create(); - - _testCasePath = Path.Combine( - Directory.GetParent( - Environment.CurrentDirectory)!.Parent!.Parent!.FullName, - Path.Combine("Rules", nameof(PublicProcedureRequiresDocumentation))); - } - - [Test] - [TestCase("Procedure")] - [TestCase("ProcedureWithComment")] - [TestCase("ProcedureWithAttribute")] - public async Task HasDiagnostic(string testCase) - { - var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(HasDiagnostic), $"{testCase}.al")) - .ConfigureAwait(false); - - _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.PublicProcedureRequiresDocumentation); - } - - [Test] - [TestCase("CodeunitAccessInternal")] - [TestCase("ProcedureDocumentationComment")] - [TestCase("ProcedureDocumentationCommentWithAttribute")] - [TestCase("ProcedureDocumentationCommentWithMultipleAttributes")] - [TestCase("ProcedureInternal")] - [TestCase("ProcedureLocal")] - [TestCase("TestCodeunit")] - [TestCase("TestCodeunitHandlerMethod")] - public async Task NoDiagnostic(string testCase) - { - var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(NoDiagnostic), $"{testCase}.al")) - .ConfigureAwait(false); - - _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.PublicProcedureRequiresDocumentation); - } - } -} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop/ALCops.DocumentationCopAnalyzers.resx b/src/ALCops.DocumentationCop/ALCops.DocumentationCopAnalyzers.resx index 3d4a5d3a..47f1c352 100644 --- a/src/ALCops.DocumentationCop/ALCops.DocumentationCopAnalyzers.resx +++ b/src/ALCops.DocumentationCop/ALCops.DocumentationCopAnalyzers.resx @@ -162,4 +162,13 @@ The XML documentation for a procedure must accurately reflect its signature. + + Internal procedures must have XML documentation + + + Internal procedure '{0}' must have XML documentation (or be restricted to local scope). + + + Internal procedures can be part of the exposed API (internalsVisibleTo) and should be explicitly documented to justify their availability. XML documentation comments communicate intent, usage, and guarantees to consumers of the extension. + \ No newline at end of file diff --git a/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs new file mode 100644 index 00000000..d1f32233 --- /dev/null +++ b/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs @@ -0,0 +1,73 @@ +using System.Collections.Immutable; +using ALCops.Common.Extensions; +using ALCops.Common.Reflection; +using Microsoft.Dynamics.Nav.CodeAnalysis; +using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; +using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax; + +namespace ALCops.DocumentationCop.Analyzers; + +[DiagnosticAnalyzer] +public sealed class ProcedureRequiresDocumentation : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create( + DiagnosticDescriptors.PublicProcedureRequiresDocumentation, + DiagnosticDescriptors.InternalProcedureRequiresDocumentation); + + public override void Initialize(AnalysisContext context) => + context.RegisterSyntaxNodeAction( + AnalyzeProcedures, + EnumProvider.SyntaxKind.MethodDeclaration + ); + + private void AnalyzeProcedures(SyntaxNodeAnalysisContext ctx) + { + if (ctx.IsObsolete() || ctx.Node is not MethodDeclarationSyntax method) + return; + + var containingObject = ctx.ContainingSymbol.GetContainingObjectTypeSymbol(); + + if (IsTestCodeunit(containingObject)) + return; + + var accessibilityToken = method.ProcedureKeyword.GetPreviousToken(); + + if (accessibilityToken.Kind == EnumProvider.SyntaxKind.LocalKeyword) + return; + + if (HasXmlDocumentation(method)) + return; + + if ((accessibilityToken.Kind == EnumProvider.SyntaxKind.InternalKeyword) || + (containingObject.DeclaredAccessibility == EnumProvider.Accessibility.Internal)) + { + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.InternalProcedureRequiresDocumentation, + method.Name.GetLocation(), + method.Name.Identifier.ToString())); + } + else + { + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.PublicProcedureRequiresDocumentation, + method.Name.GetLocation(), + method.Name.Identifier.ToString())); + } + } + + private static bool HasXmlDocumentation(MethodDeclarationSyntax method) + { + var trivia = method.GetLeadingTrivia(); + + return trivia.Any(t => + t.Kind == EnumProvider.SyntaxKind.SingleLineDocumentationCommentTrivia || + t.Kind == EnumProvider.SyntaxKind.MultiLineDocumentationCommentTrivia); + } + + private static bool IsTestCodeunit(IObjectTypeSymbol symbol) + { + var subtype = symbol.GetEnumPropertyValue(EnumProvider.PropertyKind.Subtype); + return subtype is not null && subtype == EnumProvider.CodeunitSubtypeKind.Test; + } +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop/Analyzers/PublicProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop/Analyzers/PublicProcedureRequiresDocumentation.cs deleted file mode 100644 index 9c86bd24..00000000 --- a/src/ALCops.DocumentationCop/Analyzers/PublicProcedureRequiresDocumentation.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Immutable; -using ALCops.Common.Extensions; -using ALCops.Common.Reflection; -using Microsoft.Dynamics.Nav.CodeAnalysis; -using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; -using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax; - -namespace ALCops.DocumentationCop.Analyzers; - -[DiagnosticAnalyzer] -public sealed class PublicProcedureRequiresDocumentation : DiagnosticAnalyzer -{ - public override ImmutableArray SupportedDiagnostics { get; } = - ImmutableArray.Create( - DiagnosticDescriptors.PublicProcedureRequiresDocumentation); - - public override void Initialize(AnalysisContext context) => - context.RegisterSyntaxNodeAction( - AnalyzePublicProcedures, - EnumProvider.SyntaxKind.MethodDeclaration - ); - - private void AnalyzePublicProcedures(SyntaxNodeAnalysisContext ctx) - { - if (ctx.IsObsolete() || ctx.Node is not MethodDeclarationSyntax method) - return; - - var containingObject = ctx.ContainingSymbol.GetContainingObjectTypeSymbol(); - - // Rule applies only when the containing object itself is public - if (containingObject.DeclaredAccessibility != EnumProvider.Accessibility.Public) - return; - - if (IsTestCodeunit(containingObject)) - return; - - var accessibilityToken = method.ProcedureKeyword.GetPreviousToken(); - if (accessibilityToken.Kind == EnumProvider.SyntaxKind.LocalKeyword || - accessibilityToken.Kind == EnumProvider.SyntaxKind.InternalKeyword) - return; - - if (HasXmlDocumentation(method)) - return; - - ctx.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.PublicProcedureRequiresDocumentation, - method.Name.GetLocation(), - method.Name.Identifier.ToString())); - } - - private static bool HasXmlDocumentation(MethodDeclarationSyntax method) - { - var trivia = method.GetLeadingTrivia(); - - return trivia.Any(t => - t.Kind == EnumProvider.SyntaxKind.SingleLineDocumentationCommentTrivia || - t.Kind == EnumProvider.SyntaxKind.MultiLineDocumentationCommentTrivia); - } - - private static bool IsTestCodeunit(IObjectTypeSymbol symbol) - { - var subtype = symbol.GetEnumPropertyValue(EnumProvider.PropertyKind.Subtype); - return subtype is not null && subtype == EnumProvider.CodeunitSubtypeKind.Test; - } -} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs b/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs index cf23b60e..22df25fe 100644 --- a/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs +++ b/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs @@ -55,6 +55,16 @@ public static class DiagnosticDescriptors description: DocumentationCopAnalyzers.XmlDocumentationProcedureConsistencyDescription, helpLinkUri: GetHelpUri(DiagnosticIds.XmlDocumentationProcedureConsistency)); + public static readonly DiagnosticDescriptor InternalProcedureRequiresDocumentation = new( + id: DiagnosticIds.InternalProcedureRequiresDocumentation, + title: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationTitle, + messageFormat: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationMessageFormat, + category: Category.Design, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationDescription, + helpLinkUri: GetHelpUri(DiagnosticIds.InternalProcedureRequiresDocumentation)); + public static string GetHelpUri(string identifier) { return string.Format(CultureInfo.InvariantCulture, "https://alcops.dev/docs/analyzers/documentationcop/{0}/", identifier.ToLower()); diff --git a/src/ALCops.DocumentationCop/DiagnosticIds.cs b/src/ALCops.DocumentationCop/DiagnosticIds.cs index 4b25f7c0..0858c85c 100644 --- a/src/ALCops.DocumentationCop/DiagnosticIds.cs +++ b/src/ALCops.DocumentationCop/DiagnosticIds.cs @@ -7,4 +7,5 @@ public static class DiagnosticIds public static readonly string EmptyStatementRequiresComment = "DC0003"; public static readonly string PublicProcedureRequiresDocumentation = "DC0004"; public static readonly string XmlDocumentationProcedureConsistency = "DC0005"; + public static readonly string InternalProcedureRequiresDocumentation = "DC0006"; } \ No newline at end of file From 0d0b7e526f7f239bf8d67bb1255c0a6f783785f3 Mon Sep 17 00:00:00 2001 From: Carsten Scholling Date: Wed, 17 Jun 2026 13:50:54 +0200 Subject: [PATCH 2/4] #341 DC0007-DC0010 Public objects, internal objects, business events, integration events, internal events must include XML documentation comments --- .../IntegrationEventInInternalCodeunit.cs | 7 +- .../TableDataAccessRequiresPermissions.cs | 2 +- .../TableDataAccessUnusedPermissions.cs | 2 +- ...tionObjectTypeSymbolInterfaceExtensions.cs | 37 +++++ .../MethodSymbolInterfaceExtensions.cs | 13 ++ .../Extensions/SyntaxNodeExtensions.cs | 12 ++ .../Permissions/RequiredPermissionDetector.cs | 15 -- src/ALCops.Common/Reflection/EnumProvider.cs | 3 + .../InternalHasDiagnostic/InternalCodeunit.al | 8 ++ .../InternalNoDiagnostic/InternalCodeunit.al | 11 ++ .../ObjectRequiresDocumentation.cs} | 42 ++---- .../PublicHasDiagnostic/PublicCodeunit.al | 6 + .../PublicNoDiagnostic/PublicCodeunit.al | 9 ++ .../BusinessEvent.al | 7 + .../BusinessEventWithComment.al | 8 ++ .../BusinessEventWithParameters.al | 7 + .../IntegrationEvent.al | 7 + .../IntegrationEventWithComment.al | 8 ++ .../IntegrationEventWithParameters.al | 7 + .../BusinessEvent.al | 10 ++ .../BusinessEventWithParameters.al | 13 ++ .../IntegrationEvent.al | 10 ++ .../IntegrationEventWithParameters.al | 13 ++ .../InternalEvent.al | 7 + .../InternalEventWithComment.al | 8 ++ .../InternalEventWithParameters.al | 7 + .../InternalEvent.al | 10 ++ .../InternalEventWithParameters.al | 13 ++ .../CodeunitAccessInternal.al | 0 ...nitAccessInternalProcedureWithAttribute.al | 0 ...eunitAccessInternalProcedureWithComment.al | 0 .../InternalProcedure.al | 0 .../InternalProcedureWithAttribute.al | 0 .../InternalProcedureWithComment.al | 0 ...ssInternalProcedureDocumentationComment.al | 0 ...cedureDocumentationCommentWithAttribute.al | 0 ...umentationCommentWithMultipleAttributes.al | 0 .../InternalNoDiagnostic/Procedure.al | 0 .../InternalNoDiagnostic/ProcedureLocal.al | 0 .../InternalNoDiagnostic/TestCodeunit.al | 0 .../TestCodeunitHandlerMethod.al | 0 .../ProcedureRequiresDocumentation.cs | 133 ++++++++++++++++++ .../PublicHasDiagnostic/Procedure.al | 0 .../ProcedureWithAttribute.al | 0 .../ProcedureWithComment.al | 0 .../CodeunitAccessInternal.al | 0 .../ProcedureDocumentationComment.al | 0 ...cedureDocumentationCommentWithAttribute.al | 0 ...umentationCommentWithMultipleAttributes.al | 0 .../PublicNoDiagnostic/ProcedureInternal.al | 0 .../PublicNoDiagnostic/ProcedureLocal.al | 0 .../PublicNoDiagnostic/TestCodeunit.al | 0 .../TestCodeunitHandlerMethod.al | 0 .../ALCops.DocumentationCopAnalyzers.resx | 44 +++++- .../Analyzers/ObjectRequiresDocumentation.cs | 70 +++++++++ .../ProcedureRequiresDocumentation.cs | 51 +++++-- .../DiagnosticDescriptors.cs | 42 +++++- src/ALCops.DocumentationCop/DiagnosticIds.cs | 4 + .../Analyzers/ApplicationAreaRedundancy.cs | 2 +- 59 files changed, 577 insertions(+), 71 deletions(-) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/InternalHasDiagnostic/InternalCodeunit.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/InternalNoDiagnostic/InternalCodeunit.al rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs => ObjectRequiresDocumentation/ObjectRequiresDocumentation.cs} (50%) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/PublicHasDiagnostic/PublicCodeunit.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/PublicNoDiagnostic/PublicCodeunit.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEvent.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEventWithComment.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEventWithParameters.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEvent.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEventWithComment.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEventWithParameters.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/BusinessEvent.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/BusinessEventWithParameters.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/IntegrationEvent.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/IntegrationEventWithParameters.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEvent.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEventWithComment.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEventWithParameters.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventNoDiagnostic/InternalEvent.al create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventNoDiagnostic/InternalEventWithParameters.al rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalHasDiagnostic/CodeunitAccessInternal.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalHasDiagnostic/InternalProcedure.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalHasDiagnostic/InternalProcedureWithAttribute.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalHasDiagnostic/InternalProcedureWithComment.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalNoDiagnostic/Procedure.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalNoDiagnostic/ProcedureLocal.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalNoDiagnostic/TestCodeunit.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/InternalNoDiagnostic/TestCodeunitHandlerMethod.al (100%) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicHasDiagnostic/Procedure.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicHasDiagnostic/ProcedureWithAttribute.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicHasDiagnostic/ProcedureWithComment.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicNoDiagnostic/CodeunitAccessInternal.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicNoDiagnostic/ProcedureDocumentationComment.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicNoDiagnostic/ProcedureDocumentationCommentWithAttribute.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicNoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicNoDiagnostic/ProcedureInternal.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicNoDiagnostic/ProcedureLocal.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicNoDiagnostic/TestCodeunit.al (100%) rename src/ALCops.DocumentationCop.Test/Rules/{PublicProcedureRequiresDocumentation => ProcedureRequiresDocumentation}/PublicNoDiagnostic/TestCodeunitHandlerMethod.al (100%) create mode 100644 src/ALCops.DocumentationCop/Analyzers/ObjectRequiresDocumentation.cs diff --git a/src/ALCops.ApplicationCop/Analyzers/IntegrationEventInInternalCodeunit.cs b/src/ALCops.ApplicationCop/Analyzers/IntegrationEventInInternalCodeunit.cs index 925a35d1..d6357fa3 100644 --- a/src/ALCops.ApplicationCop/Analyzers/IntegrationEventInInternalCodeunit.cs +++ b/src/ALCops.ApplicationCop/Analyzers/IntegrationEventInInternalCodeunit.cs @@ -28,7 +28,8 @@ private void AnalyzeIntegrationEvent(SymbolAnalysisContext ctx) return; IApplicationObjectTypeSymbol? applicationObject = methodSymbol.GetContainingApplicationObjectTypeSymbol(); - if (applicationObject is null || !IsInternalCodeunit(applicationObject) || applicationObject.IsObsolete()) + + if (applicationObject is null || !applicationObject.IsInternalCodeunit()) return; if (!IsIntegrationEvent(methodSymbol)) @@ -41,10 +42,6 @@ private void AnalyzeIntegrationEvent(SymbolAnalysisContext ctx) applicationObject.Name)); } - private static bool IsInternalCodeunit(IApplicationObjectTypeSymbol applicationObject) => - applicationObject.Kind == EnumProvider.SymbolKind.Codeunit && - applicationObject.DeclaredAccessibility == EnumProvider.Accessibility.Internal; - private static bool IsIntegrationEvent(IMethodSymbol methodSymbol) => methodSymbol.Attributes.Any(attr => attr.AttributeKind == EnumProvider.AttributeKind.IntegrationEvent); } \ No newline at end of file diff --git a/src/ALCops.ApplicationCop/Analyzers/TableDataAccessRequiresPermissions.cs b/src/ALCops.ApplicationCop/Analyzers/TableDataAccessRequiresPermissions.cs index cbaf0acd..4969be8d 100644 --- a/src/ALCops.ApplicationCop/Analyzers/TableDataAccessRequiresPermissions.cs +++ b/src/ALCops.ApplicationCop/Analyzers/TableDataAccessRequiresPermissions.cs @@ -48,7 +48,7 @@ private void AnalyzeInvocation(OperationAnalysisContext ctx) if (required is null) return; - if (RequiredPermissionDetector.IsTestCodeunitWithPermissionsDisabled(containingObject)) + if (containingObject.IsTestCodeunitWithPermissionsDisabled()) return; var pageContext = PermissionResolver.GetPageContext(containingObject); diff --git a/src/ALCops.ApplicationCop/Analyzers/TableDataAccessUnusedPermissions.cs b/src/ALCops.ApplicationCop/Analyzers/TableDataAccessUnusedPermissions.cs index 4e605edb..eeeb184b 100644 --- a/src/ALCops.ApplicationCop/Analyzers/TableDataAccessUnusedPermissions.cs +++ b/src/ALCops.ApplicationCop/Analyzers/TableDataAccessUnusedPermissions.cs @@ -42,7 +42,7 @@ private static void AnalyzeApplicationObject(SyntaxNodeAnalysisContext ctx) if (containingObject.Kind == EnumProvider.SymbolKind.PermissionSet || containingObject.Kind == EnumProvider.SymbolKind.PermissionSetExtension || containingObject.IsObsolete() - || RequiredPermissionDetector.IsTestCodeunitWithPermissionsDisabled(containingObject)) + || containingObject.IsTestCodeunitWithPermissionsDisabled()) return; var permissionsProperty = containingObject.GetProperty(EnumProvider.PropertyKind.Permissions); diff --git a/src/ALCops.Common/Extensions/ApplicationObjectTypeSymbolInterfaceExtensions.cs b/src/ALCops.Common/Extensions/ApplicationObjectTypeSymbolInterfaceExtensions.cs index e1522a7e..1c776220 100644 --- a/src/ALCops.Common/Extensions/ApplicationObjectTypeSymbolInterfaceExtensions.cs +++ b/src/ALCops.Common/Extensions/ApplicationObjectTypeSymbolInterfaceExtensions.cs @@ -44,6 +44,43 @@ public static bool MethodImplementsInterfaceMethod(this IApplicationObjectTypeSy return false; } + /// + /// Returns true if the object is a test codeunit with TestPermissions = Disabled. + /// + public static bool IsTestCodeunitWithPermissionsDisabled(this IApplicationObjectTypeSymbol? containingObject) + { + if (!containingObject.IsTestCodeunit()) + { + return false; + } + + var testPermissions = (containingObject as ICodeunitTypeSymbol)?.GetEnumPropertyValue(EnumProvider.PropertyKind.TestPermissions); + + return testPermissions is not null && testPermissions == EnumProvider.TestPermissionsKind.Disabled; + } + + /// + /// Returns true if the object is a test codeunit. + /// + public static bool IsTestCodeunit(this IApplicationObjectTypeSymbol? objectSymbol) + { + if (objectSymbol is not ICodeunitTypeSymbol codeunit) + { + return false; + } + + var subtype = codeunit.GetEnumPropertyValue(EnumProvider.PropertyKind.Subtype); + + return (subtype is not null) && (subtype == EnumProvider.CodeunitSubtypeKind.Test); + } + + /// + /// Returns true if the object is a codeunit with accessability internal. + /// + public static bool IsInternalCodeunit(this IApplicationObjectTypeSymbol applicationObject) => + applicationObject.Kind == EnumProvider.SymbolKind.Codeunit && + applicationObject.DeclaredAccessibility == EnumProvider.Accessibility.Internal; + /// /// Gets the flattened list of all xmlport nodes (including deeply nested) for xmlport objects. /// Uses reflection to access the internal SourceXmlPortTypeSymbol.FlattenedNodes property, diff --git a/src/ALCops.Common/Extensions/MethodSymbolInterfaceExtensions.cs b/src/ALCops.Common/Extensions/MethodSymbolInterfaceExtensions.cs index b01f7421..9bd179a7 100644 --- a/src/ALCops.Common/Extensions/MethodSymbolInterfaceExtensions.cs +++ b/src/ALCops.Common/Extensions/MethodSymbolInterfaceExtensions.cs @@ -22,6 +22,19 @@ public static bool MethodImplementsInterfaceMethod(this IMethodSymbol methodSymb public static bool IsHandler(this IMethodSymbol method) => method.GetPropertyIfExists("IsHandler"); + /// + /// Checks whether the method is an IntegrationEvent or BusinessEvent. + /// + public static bool IsIntegrationOrBusinessEvent(this IMethodSymbol methodSymbol) => + methodSymbol.Attributes.Any(attr => (attr.AttributeKind == EnumProvider.AttributeKind.IntegrationEvent) || (attr.AttributeKind == EnumProvider.AttributeKind.BusinessEvent)); + + /// + /// Checks whether the method is an InternalEvent. + /// + public static bool IsInternalEvent(this IMethodSymbol methodSymbol) => + methodSymbol.Attributes.Any(attr => attr.AttributeKind == EnumProvider.AttributeKind.InternalEvent); + + public static bool MethodImplementsInterfaceMethod(this IMethodSymbol methodSymbol, IMethodSymbol interfaceMethodSymbol) { if (methodSymbol is null || interfaceMethodSymbol is null) diff --git a/src/ALCops.Common/Extensions/SyntaxNodeExtensions.cs b/src/ALCops.Common/Extensions/SyntaxNodeExtensions.cs index 6fe2b951..3007fd61 100644 --- a/src/ALCops.Common/Extensions/SyntaxNodeExtensions.cs +++ b/src/ALCops.Common/Extensions/SyntaxNodeExtensions.cs @@ -6,6 +6,18 @@ namespace ALCops.Common.Extensions; public static class SyntaxNodeExtensions { + /// + /// Checks whether the method is an IntegrationEvent or BusinessEvent. + /// + public static bool IsIntegrationOrBusinessEvent(this MethodDeclarationSyntax methodSyntax) => + methodSyntax.Attributes.Any(a => (a.Name.Identifier.ToString() == "IntegrationEvent") || (a.Name.Identifier.ToString() == "BusinessEvent")); + + /// + /// Checks whether the method is an InternalEvent. + /// + public static bool IsInternalEvent(this MethodDeclarationSyntax methodSyntax) => + methodSyntax.Attributes.Any(a => a.Name.Identifier.ToString() == "InternalEvent"); + public static int? GetIntegerPropertyValue(this LabelPropertyValueSyntax? labelProperty, IdentifierProperty property) => labelProperty?.Value.GetIntegerPropertyValue(property); diff --git a/src/ALCops.Common/Permissions/RequiredPermissionDetector.cs b/src/ALCops.Common/Permissions/RequiredPermissionDetector.cs index 17735269..087fc293 100644 --- a/src/ALCops.Common/Permissions/RequiredPermissionDetector.cs +++ b/src/ALCops.Common/Permissions/RequiredPermissionDetector.cs @@ -125,21 +125,6 @@ public static IEnumerable GetFromXmlPortNode(ISymbol symbol, /// public static bool IsSystemTable(ITableTypeSymbol table) => table.Id > 2000000000; - /// - /// Returns true if the object is a test codeunit with TestPermissions = Disabled. - /// - public static bool IsTestCodeunitWithPermissionsDisabled(IApplicationObjectTypeSymbol? containingObject) - { - if (containingObject is not ICodeunitTypeSymbol codeunit) - return false; - - var subtype = codeunit.GetEnumPropertyValue(EnumProvider.PropertyKind.Subtype); - if (subtype is null || subtype != EnumProvider.CodeunitSubtypeKind.Test) - return false; - - var testPermissions = codeunit.GetEnumPropertyValue(EnumProvider.PropertyKind.TestPermissions); - return testPermissions is not null && testPermissions == EnumProvider.TestPermissionsKind.Disabled; - } private static DirectionKind ResolveXmlPortDirection(IXmlPortTypeSymbol xmlPort) { diff --git a/src/ALCops.Common/Reflection/EnumProvider.cs b/src/ALCops.Common/Reflection/EnumProvider.cs index bfe14f68..6f27aca0 100644 --- a/src/ALCops.Common/Reflection/EnumProvider.cs +++ b/src/ALCops.Common/Reflection/EnumProvider.cs @@ -796,6 +796,8 @@ public static class SymbolKind new(() => ParseEnum(nameof(NavCodeAnalysis.SymbolKind.Codeunit))); private static readonly Lazy _control = new(() => ParseEnum(nameof(NavCodeAnalysis.SymbolKind.Control))); + private static readonly Lazy _controlAddIn = + new(() => ParseEnum(nameof(NavCodeAnalysis.SymbolKind.ControlAddIn))); private static readonly Lazy _entitlement = new(() => ParseEnum(nameof(NavCodeAnalysis.SymbolKind.Entitlement))); private static readonly Lazy _enum = @@ -864,6 +866,7 @@ public static class SymbolKind public static NavCodeAnalysis.SymbolKind Action => _action.Value; public static NavCodeAnalysis.SymbolKind Class => _class.Value; public static NavCodeAnalysis.SymbolKind Codeunit => _codeunit.Value; + public static NavCodeAnalysis.SymbolKind ControlAddIn => _controlAddIn.Value; public static NavCodeAnalysis.SymbolKind Control => _control.Value; public static NavCodeAnalysis.SymbolKind Entitlement => _entitlement.Value; public static NavCodeAnalysis.SymbolKind Enum => _enum.Value; diff --git a/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/InternalHasDiagnostic/InternalCodeunit.al b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/InternalHasDiagnostic/InternalCodeunit.al new file mode 100644 index 00000000..b76b2bc5 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/InternalHasDiagnostic/InternalCodeunit.al @@ -0,0 +1,8 @@ +codeunit 50100 [|MyCodeunit|] +{ + Access = Internal; + + procedure MyProcedure() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/InternalNoDiagnostic/InternalCodeunit.al b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/InternalNoDiagnostic/InternalCodeunit.al new file mode 100644 index 00000000..7847b803 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/InternalNoDiagnostic/InternalCodeunit.al @@ -0,0 +1,11 @@ +/// +/// A great piece of code that solves all problems. +/// +codeunit 50100 [|MyCodeunit|] +{ + Access = Internal; + + procedure MyProcedure() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.cs similarity index 50% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs rename to src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.cs index aae7185b..0473f13c 100644 --- a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs +++ b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.cs @@ -2,7 +2,7 @@ namespace ALCops.DocumentationCop.Test { - public class PublicProcedureRequiresDocumentation : NavCodeAnalysisBase + public class ObjectRequiresDocumentation : NavCodeAnalysisBase { private AnalyzerTestFixture _fixture; private string _testCasePath; @@ -10,72 +10,52 @@ public class PublicProcedureRequiresDocumentation : NavCodeAnalysisBase [SetUp] public void Setup() { - _fixture = RoslynFixtureFactory.Create(); + _fixture = RoslynFixtureFactory.Create(); _testCasePath = Path.Combine( Directory.GetParent( Environment.CurrentDirectory)!.Parent!.Parent!.FullName, - Path.Combine("Rules", nameof(PublicProcedureRequiresDocumentation))); + Path.Combine("Rules", nameof(ObjectRequiresDocumentation))); } [Test] - [TestCase("Procedure")] - [TestCase("ProcedureWithAttribute")] - [TestCase("ProcedureWithComment")] + [TestCase("PublicCodeunit")] public async Task PublicHasDiagnostic(string testCase) { var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(PublicHasDiagnostic), $"{testCase}.al")) .ConfigureAwait(false); - _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.PublicProcedureRequiresDocumentation); + _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.PublicObjectRequiresDocumentation); } [Test] - [TestCase("CodeunitAccessInternal")] - [TestCase("ProcedureDocumentationComment")] - [TestCase("ProcedureDocumentationCommentWithAttribute")] - [TestCase("ProcedureDocumentationCommentWithMultipleAttributes")] - [TestCase("ProcedureInternal")] - [TestCase("ProcedureLocal")] - [TestCase("TestCodeunit")] - [TestCase("TestCodeunitHandlerMethod")] + [TestCase("PublicCodeunit")] public async Task PublicNoDiagnostic(string testCase) { var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(PublicNoDiagnostic), $"{testCase}.al")) .ConfigureAwait(false); - _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.PublicProcedureRequiresDocumentation); + _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.PublicObjectRequiresDocumentation); } [Test] - [TestCase("CodeunitAccessInternal")] - [TestCase("CodeunitAccessInternalProcedureWithAttribute")] - [TestCase("CodeunitAccessInternalProcedureWithComment")] - [TestCase("InternalProcedure")] - [TestCase("InternalProcedureWithAttribute")] - [TestCase("InternalProcedureWithComment")] + [TestCase("InternalCodeunit")] public async Task InternalHasDiagnostic(string testCase) { var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(InternalHasDiagnostic), $"{testCase}.al")) .ConfigureAwait(false); - _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.InternalProcedureRequiresDocumentation); + _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.InternalObjectRequiresDocumentation); } [Test] - [TestCase("CodeunitAccessInternalProcedureDocumentationComment")] - [TestCase("CodeunitAccessInternalProcedureDocumentationCommentWithAttribute")] - [TestCase("CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes")] - [TestCase("Procedure")] - [TestCase("ProcedureLocal")] - [TestCase("TestCodeunit")] - [TestCase("TestCodeunitHandlerMethod")] + [TestCase("InternalCodeunit")] public async Task InternalNoDiagnostic(string testCase) { var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(InternalNoDiagnostic), $"{testCase}.al")) .ConfigureAwait(false); - _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.InternalProcedureRequiresDocumentation); + _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.InternalObjectRequiresDocumentation); } } } diff --git a/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/PublicHasDiagnostic/PublicCodeunit.al b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/PublicHasDiagnostic/PublicCodeunit.al new file mode 100644 index 00000000..3e8c38bc --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/PublicHasDiagnostic/PublicCodeunit.al @@ -0,0 +1,6 @@ +codeunit 50100 [|MyCodeunit|] +{ + procedure MyProcedure() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/PublicNoDiagnostic/PublicCodeunit.al b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/PublicNoDiagnostic/PublicCodeunit.al new file mode 100644 index 00000000..f053326b --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/PublicNoDiagnostic/PublicCodeunit.al @@ -0,0 +1,9 @@ +/// +/// A great piece of code that solves all problems. +/// +codeunit 50100 [|MyCodeunit|] +{ + procedure MyProcedure() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEvent.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEvent.al new file mode 100644 index 00000000..1fada342 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEvent.al @@ -0,0 +1,7 @@ +codeunit 50100 MyCodeunit +{ + [BusinessEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEventWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEventWithComment.al new file mode 100644 index 00000000..e9ae55bc --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEventWithComment.al @@ -0,0 +1,8 @@ +codeunit 50100 MyCodeunit +{ + // This is a comment + [BusinessEvent(false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEventWithParameters.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEventWithParameters.al new file mode 100644 index 00000000..71bfe5c6 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEventWithParameters.al @@ -0,0 +1,7 @@ +codeunit 50100 MyCodeunit +{ + [BusinessEvent(false)] + local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text) + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEvent.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEvent.al new file mode 100644 index 00000000..937219bc --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEvent.al @@ -0,0 +1,7 @@ +codeunit 50100 MyCodeunit +{ + [IntegrationEvent(false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEventWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEventWithComment.al new file mode 100644 index 00000000..5c88ff76 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEventWithComment.al @@ -0,0 +1,8 @@ +codeunit 50100 MyCodeunit +{ + // This is a comment + [IntegrationEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEventWithParameters.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEventWithParameters.al new file mode 100644 index 00000000..8e78d783 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEventWithParameters.al @@ -0,0 +1,7 @@ +codeunit 50100 MyCodeunit +{ + [IntegrationEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text) + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/BusinessEvent.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/BusinessEvent.al new file mode 100644 index 00000000..ca68cba5 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/BusinessEvent.al @@ -0,0 +1,10 @@ +codeunit 50100 MyCodeunit +{ + /// + /// Triggered just before the world goes light. + /// + [BusinessEvent(false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/BusinessEventWithParameters.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/BusinessEventWithParameters.al new file mode 100644 index 00000000..e1ff03a8 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/BusinessEventWithParameters.al @@ -0,0 +1,13 @@ +codeunit 50100 MyCodeunit +{ + /// + /// Triggered just before the world goes light. + /// + /// An integer value. + /// A decimal value. + /// A text to return. + [BusinessEvent(false)] + local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text) + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/IntegrationEvent.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/IntegrationEvent.al new file mode 100644 index 00000000..a6c7caeb --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/IntegrationEvent.al @@ -0,0 +1,10 @@ +codeunit 50100 MyCodeunit +{ + /// + /// Triggered just before the world goes light. + /// + [IntegrationEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/IntegrationEventWithParameters.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/IntegrationEventWithParameters.al new file mode 100644 index 00000000..c572759d --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventNoDiagnostic/IntegrationEventWithParameters.al @@ -0,0 +1,13 @@ +codeunit 50100 MyCodeunit +{ + /// + /// Triggered just before the world goes light. + /// + /// An integer value. + /// A decimal value. + /// A text to return. + [IntegrationEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text) + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEvent.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEvent.al new file mode 100644 index 00000000..ba5eead3 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEvent.al @@ -0,0 +1,7 @@ +codeunit 50100 MyCodeunit +{ + [InternalEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEventWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEventWithComment.al new file mode 100644 index 00000000..44d579ff --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEventWithComment.al @@ -0,0 +1,8 @@ +codeunit 50100 MyCodeunit +{ + // This is a comment + [InternalEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEventWithParameters.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEventWithParameters.al new file mode 100644 index 00000000..621af1e0 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventHasDiagnostic/InternalEventWithParameters.al @@ -0,0 +1,7 @@ +codeunit 50100 MyCodeunit +{ + [InternalEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text) + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventNoDiagnostic/InternalEvent.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventNoDiagnostic/InternalEvent.al new file mode 100644 index 00000000..de55ac36 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventNoDiagnostic/InternalEvent.al @@ -0,0 +1,10 @@ +codeunit 50100 MyCodeunit +{ + /// + /// Triggered just before the world goes light. + /// + [InternalEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|]() + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventNoDiagnostic/InternalEventWithParameters.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventNoDiagnostic/InternalEventWithParameters.al new file mode 100644 index 00000000..fbf6215e --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalEventNoDiagnostic/InternalEventWithParameters.al @@ -0,0 +1,13 @@ +codeunit 50100 MyCodeunit +{ + /// + /// Triggered just before the world goes light. + /// + /// An integer value. + /// A decimal value. + /// A text to return. + [InternalEvent(false, false)] + local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text) + begin + end; +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternal.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternal.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternal.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternal.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithAttribute.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/CodeunitAccessInternalProcedureWithComment.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedure.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedure.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedure.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedure.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithAttribute.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithAttribute.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithAttribute.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithComment.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithComment.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalHasDiagnostic/InternalProcedureWithComment.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationComment.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithAttribute.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/Procedure.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/Procedure.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/Procedure.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/Procedure.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/ProcedureLocal.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/ProcedureLocal.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/ProcedureLocal.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/ProcedureLocal.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunit.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunit.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunit.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunit.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunitHandlerMethod.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunitHandlerMethod.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunitHandlerMethod.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/InternalNoDiagnostic/TestCodeunitHandlerMethod.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs new file mode 100644 index 00000000..8b7606c6 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs @@ -0,0 +1,133 @@ +using RoslynTestKit; + +namespace ALCops.DocumentationCop.Test +{ + public class ProcedureRequiresDocumentation : NavCodeAnalysisBase + { + private AnalyzerTestFixture _fixture; + private string _testCasePath; + + [SetUp] + public void Setup() + { + _fixture = RoslynFixtureFactory.Create(); + + _testCasePath = Path.Combine( + Directory.GetParent( + Environment.CurrentDirectory)!.Parent!.Parent!.FullName, + Path.Combine("Rules", nameof(ProcedureRequiresDocumentation))); + } + + [Test] + [TestCase("BusinessEvent")] + [TestCase("BusinessEventWithComment")] + [TestCase("BusinessEventWithParameters")] + [TestCase("IntegrationEvent")] + [TestCase("IntegrationEventWithComment")] + [TestCase("IntegrationEventWithParameters")] + public async Task IntegrationEventHasDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(IntegrationEventHasDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.EventRequiresDocumentation); + } + + [Test] + [TestCase("BusinessEvent")] + [TestCase("BusinessEventWithParameters")] + [TestCase("IntegrationEvent")] + [TestCase("IntegrationEventWithParameters")] + public async Task IntegrationEventNoDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(IntegrationEventNoDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.EventRequiresDocumentation); + } + + [Test] + [TestCase("InternalEvent")] + [TestCase("InternalEventWithComment")] + [TestCase("InternalEventWithParameters")] + public async Task InternalEventHasDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(InternalEventHasDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.InternalEventRequiresDocumentation); + } + + [Test] + [TestCase("InternalEvent")] + [TestCase("InternalEventWithParameters")] + public async Task InternalEventNoDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(InternalEventNoDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.InternalEventRequiresDocumentation); + } + + //----------------------- + [Test] + [TestCase("CodeunitAccessInternal")] + [TestCase("CodeunitAccessInternalProcedureWithAttribute")] + [TestCase("CodeunitAccessInternalProcedureWithComment")] + [TestCase("InternalProcedure")] + [TestCase("InternalProcedureWithAttribute")] + [TestCase("InternalProcedureWithComment")] + public async Task InternalHasDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(InternalHasDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.InternalProcedureRequiresDocumentation); + } + + [Test] + [TestCase("CodeunitAccessInternalProcedureDocumentationComment")] + [TestCase("CodeunitAccessInternalProcedureDocumentationCommentWithAttribute")] + [TestCase("CodeunitAccessInternalProcedureDocumentationCommentWithMultipleAttributes")] + [TestCase("Procedure")] + [TestCase("ProcedureLocal")] + [TestCase("TestCodeunit")] + [TestCase("TestCodeunitHandlerMethod")] + public async Task InternalNoDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(InternalNoDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.InternalProcedureRequiresDocumentation); + } + + [Test] + [TestCase("Procedure")] + [TestCase("ProcedureWithAttribute")] + [TestCase("ProcedureWithComment")] + public async Task PublicHasDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(PublicHasDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.HasDiagnosticAtAllMarkers(code, DiagnosticIds.PublicProcedureRequiresDocumentation); + } + + [Test] + [TestCase("CodeunitAccessInternal")] + [TestCase("ProcedureDocumentationComment")] + [TestCase("ProcedureDocumentationCommentWithAttribute")] + [TestCase("ProcedureDocumentationCommentWithMultipleAttributes")] + [TestCase("ProcedureInternal")] + [TestCase("ProcedureLocal")] + [TestCase("TestCodeunit")] + [TestCase("TestCodeunitHandlerMethod")] + public async Task PublicNoDiagnostic(string testCase) + { + var code = await File.ReadAllTextAsync(Path.Combine(_testCasePath, nameof(PublicNoDiagnostic), $"{testCase}.al")) + .ConfigureAwait(false); + + _fixture.NoDiagnosticAtAllMarkers(code, DiagnosticIds.PublicProcedureRequiresDocumentation); + } + } +} diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/Procedure.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicHasDiagnostic/Procedure.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/Procedure.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicHasDiagnostic/Procedure.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithAttribute.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithAttribute.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithAttribute.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithComment.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithComment.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithComment.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicHasDiagnostic/ProcedureWithComment.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/CodeunitAccessInternal.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/CodeunitAccessInternal.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/CodeunitAccessInternal.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/CodeunitAccessInternal.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationComment.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationComment.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationComment.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationComment.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithAttribute.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithAttribute.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithAttribute.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithAttribute.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureDocumentationCommentWithMultipleAttributes.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureInternal.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureInternal.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureInternal.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureInternal.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureLocal.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureLocal.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureLocal.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/ProcedureLocal.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunit.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunit.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunit.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunit.al diff --git a/src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunitHandlerMethod.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunitHandlerMethod.al similarity index 100% rename from src/ALCops.DocumentationCop.Test/Rules/PublicProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunitHandlerMethod.al rename to src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/PublicNoDiagnostic/TestCodeunitHandlerMethod.al diff --git a/src/ALCops.DocumentationCop/ALCops.DocumentationCopAnalyzers.resx b/src/ALCops.DocumentationCop/ALCops.DocumentationCopAnalyzers.resx index 47f1c352..4dda25b4 100644 --- a/src/ALCops.DocumentationCop/ALCops.DocumentationCopAnalyzers.resx +++ b/src/ALCops.DocumentationCop/ALCops.DocumentationCopAnalyzers.resx @@ -136,10 +136,10 @@ Empty statements (standalone ;) reduce readability and are commonly introduced by mistake. This rule flags empty statement expressions (excess semicolons) so they can be removed. - Public procedures must have XML documentation + Public procedures must include XML documentation comments - Public procedure '{0}' must have XML documentation (or be restricted to local or internal scope). + Public procedure '{0}' must include XML documentation comments (or be restricted to local or internal scope). Public procedures are part of the exposed API and must be explicitly documented to justify their availability. XML documentation comments communicate intent, usage, and guarantees to consumers of the extension. @@ -163,12 +163,48 @@ The XML documentation for a procedure must accurately reflect its signature. - Internal procedures must have XML documentation + Internal procedures must include XML documentation comments - Internal procedure '{0}' must have XML documentation (or be restricted to local scope). + Internal procedure '{0}' must include XML documentation comments (or be restricted to local scope). Internal procedures can be part of the exposed API (internalsVisibleTo) and should be explicitly documented to justify their availability. XML documentation comments communicate intent, usage, and guarantees to consumers of the extension. + + Public objects must include XML documentation comments + + + Public object '{0}' must include XML documentation comments. + + + Public objects are part of the exposed API and must be explicitly documented to justify their availability. XML documentation comments communicate intent, usage, and guarantees to consumers of the extension. + + + Internal objects must include XML documentation comments + + + Internal object '{0}' must include XML documentation comments. + + + Internal objects can be part of the exposed API (internalsVisibleTo) and should be explicitly documented to justify their availability. XML documentation comments communicate intent, usage, and guarantees to consumers of the extension. + + + An integration or business event must include XML documentation comments + + + Event '{0}' must include XML documentation comments. + + + Integration events and business events are part of the exposed API and must be explicitly documented to justify their availability. XML documentation comments communicate intent, usage, and guarantees to consumers of the extension. + + + Internal event must include XML documentation comments + + + Internal event '{0}' must include XML documentation comments. + + + Internal events can be part of the exposed API (internalsVisibleTo) and should be explicitly documented to justify their availability. XML documentation comments communicate intent, usage, and guarantees to consumers of the extension. + \ No newline at end of file diff --git a/src/ALCops.DocumentationCop/Analyzers/ObjectRequiresDocumentation.cs b/src/ALCops.DocumentationCop/Analyzers/ObjectRequiresDocumentation.cs new file mode 100644 index 00000000..aafbbfc3 --- /dev/null +++ b/src/ALCops.DocumentationCop/Analyzers/ObjectRequiresDocumentation.cs @@ -0,0 +1,70 @@ +using System.Collections.Immutable; +using ALCops.Common.Extensions; +using ALCops.Common.Reflection; +using Microsoft.Dynamics.Nav.CodeAnalysis; +using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics; + +namespace ALCops.DocumentationCop.Analyzers; + +[DiagnosticAnalyzer] +public sealed class ObjectRequiresDocumentation : DiagnosticAnalyzer +{ + public override ImmutableArray SupportedDiagnostics { get; } = + ImmutableArray.Create( + DiagnosticDescriptors.PublicObjectRequiresDocumentation, + DiagnosticDescriptors.InternalObjectRequiresDocumentation); + + public override void Initialize(AnalysisContext context) + => context.RegisterSymbolAction( + CheckObjectDocumentation, + EnumProvider.SymbolKind.Codeunit, + EnumProvider.SymbolKind.ControlAddIn, + EnumProvider.SymbolKind.Enum, + EnumProvider.SymbolKind.Interface, + EnumProvider.SymbolKind.Page, + EnumProvider.SymbolKind.PermissionSet, + EnumProvider.SymbolKind.Profile, + EnumProvider.SymbolKind.Query, + EnumProvider.SymbolKind.Report, + EnumProvider.SymbolKind.Table, + EnumProvider.SymbolKind.XmlPort); + + private void CheckObjectDocumentation(SymbolAnalysisContext ctx) + { + if (ctx.Compilation.FileSystem is null) + { + return; + } + + if (ctx.Symbol is not IApplicationObjectTypeSymbol appObjectTypeSymbol) + { + return; + } + + if (appObjectTypeSymbol.IsTestCodeunit()) + { + return; + } + + var xmlComment = appObjectTypeSymbol.GetDocumentationCommentXml(); + + if (string.IsNullOrWhiteSpace(xmlComment)) + { + if (appObjectTypeSymbol.DeclaredAccessibility == EnumProvider.Accessibility.Public) + { + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.PublicObjectRequiresDocumentation, + appObjectTypeSymbol.GetLocation(), + appObjectTypeSymbol.Name)); + } + + else if (appObjectTypeSymbol.DeclaredAccessibility == EnumProvider.Accessibility.Internal) + { + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.InternalObjectRequiresDocumentation, + appObjectTypeSymbol.GetLocation(), + appObjectTypeSymbol.Name)); + } + } + } +} diff --git a/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs index d1f32233..95cfdcd4 100644 --- a/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs +++ b/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs @@ -13,7 +13,9 @@ public sealed class ProcedureRequiresDocumentation : DiagnosticAnalyzer public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( DiagnosticDescriptors.PublicProcedureRequiresDocumentation, - DiagnosticDescriptors.InternalProcedureRequiresDocumentation); + DiagnosticDescriptors.InternalProcedureRequiresDocumentation, + DiagnosticDescriptors.EventRequiresDocumentation, + DiagnosticDescriptors.InternalEventRequiresDocumentation); public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction( @@ -31,28 +33,52 @@ private void AnalyzeProcedures(SyntaxNodeAnalysisContext ctx) if (IsTestCodeunit(containingObject)) return; + if (HasXmlDocumentation(method)) + return; + var accessibilityToken = method.ProcedureKeyword.GetPreviousToken(); - if (accessibilityToken.Kind == EnumProvider.SyntaxKind.LocalKeyword) - return; + if (method.IsIntegrationOrBusinessEvent()) + { + var loc = method.Name.GetLocation(); + var id = method.Name.Identifier.ToString(); - if (HasXmlDocumentation(method)) - return; + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.EventRequiresDocumentation, + loc, + id)); + } - if ((accessibilityToken.Kind == EnumProvider.SyntaxKind.InternalKeyword) || - (containingObject.DeclaredAccessibility == EnumProvider.Accessibility.Internal)) + else if (method.IsInternalEvent()) { ctx.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.InternalProcedureRequiresDocumentation, + DiagnosticDescriptors.InternalEventRequiresDocumentation, method.Name.GetLocation(), method.Name.Identifier.ToString())); } + else { - ctx.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.PublicProcedureRequiresDocumentation, - method.Name.GetLocation(), - method.Name.Identifier.ToString())); + if (accessibilityToken.Kind == EnumProvider.SyntaxKind.LocalKeyword) + { + return; + } + + if ((accessibilityToken.Kind == EnumProvider.SyntaxKind.InternalKeyword) || + (containingObject.DeclaredAccessibility == EnumProvider.Accessibility.Internal)) + { + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.InternalProcedureRequiresDocumentation, + method.Name.GetLocation(), + method.Name.Identifier.ToString())); + } + else + { + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.PublicProcedureRequiresDocumentation, + method.Name.GetLocation(), + method.Name.Identifier.ToString())); + } } } @@ -68,6 +94,7 @@ private static bool HasXmlDocumentation(MethodDeclarationSyntax method) private static bool IsTestCodeunit(IObjectTypeSymbol symbol) { var subtype = symbol.GetEnumPropertyValue(EnumProvider.PropertyKind.Subtype); + return subtype is not null && subtype == EnumProvider.CodeunitSubtypeKind.Test; } } \ No newline at end of file diff --git a/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs b/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs index 22df25fe..4b66bc02 100644 --- a/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs +++ b/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs @@ -60,11 +60,51 @@ public static class DiagnosticDescriptors title: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationTitle, messageFormat: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationMessageFormat, category: Category.Design, - defaultSeverity: DiagnosticSeverity.Info, + defaultSeverity: DiagnosticSeverity.Hidden, isEnabledByDefault: true, description: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationDescription, helpLinkUri: GetHelpUri(DiagnosticIds.InternalProcedureRequiresDocumentation)); + public static readonly DiagnosticDescriptor PublicObjectRequiresDocumentation = new( + id: DiagnosticIds.PublicObjectRequiresDocumentation, + title: DocumentationCopAnalyzers.PublicObjectRequiresDocumentationTitle, + messageFormat: DocumentationCopAnalyzers.PublicObjectRequiresDocumentationMessageFormat, + category: Category.Design, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: true, + description: DocumentationCopAnalyzers.PublicObjectRequiresDocumentationDescription, + helpLinkUri: GetHelpUri(DiagnosticIds.PublicObjectRequiresDocumentation)); + + public static readonly DiagnosticDescriptor InternalObjectRequiresDocumentation = new( + id: DiagnosticIds.InternalObjectRequiresDocumentation, + title: DocumentationCopAnalyzers.InternalObjectRequiresDocumentationTitle, + messageFormat: DocumentationCopAnalyzers.InternalObjectRequiresDocumentationMessageFormat, + category: Category.Design, + defaultSeverity: DiagnosticSeverity.Hidden, + isEnabledByDefault: true, + description: DocumentationCopAnalyzers.InternalObjectRequiresDocumentationDescription, + helpLinkUri: GetHelpUri(DiagnosticIds.InternalObjectRequiresDocumentation)); + + public static readonly DiagnosticDescriptor EventRequiresDocumentation = new( + id: DiagnosticIds.EventRequiresDocumentation, + title: DocumentationCopAnalyzers.EventRequiresDocumentationTitle, + messageFormat: DocumentationCopAnalyzers.EventRequiresDocumentationMessageFormat, + category: Category.Design, + defaultSeverity: DiagnosticSeverity.Hidden, + isEnabledByDefault: true, + description: DocumentationCopAnalyzers.EventRequiresDocumentationDescription, + helpLinkUri: GetHelpUri(DiagnosticIds.EventRequiresDocumentation)); + + public static readonly DiagnosticDescriptor InternalEventRequiresDocumentation = new( + id: DiagnosticIds.InternalEventRequiresDocumentation, + title: DocumentationCopAnalyzers.InternalEventRequiresDocumentationTitle, + messageFormat: DocumentationCopAnalyzers.InternalEventRequiresDocumentationMessageFormat, + category: Category.Design, + defaultSeverity: DiagnosticSeverity.Hidden, + isEnabledByDefault: true, + description: DocumentationCopAnalyzers.InternalEventRequiresDocumentationDescription, + helpLinkUri: GetHelpUri(DiagnosticIds.InternalEventRequiresDocumentation)); + public static string GetHelpUri(string identifier) { return string.Format(CultureInfo.InvariantCulture, "https://alcops.dev/docs/analyzers/documentationcop/{0}/", identifier.ToLower()); diff --git a/src/ALCops.DocumentationCop/DiagnosticIds.cs b/src/ALCops.DocumentationCop/DiagnosticIds.cs index 0858c85c..1493e9dc 100644 --- a/src/ALCops.DocumentationCop/DiagnosticIds.cs +++ b/src/ALCops.DocumentationCop/DiagnosticIds.cs @@ -8,4 +8,8 @@ public static class DiagnosticIds public static readonly string PublicProcedureRequiresDocumentation = "DC0004"; public static readonly string XmlDocumentationProcedureConsistency = "DC0005"; public static readonly string InternalProcedureRequiresDocumentation = "DC0006"; + public static readonly string PublicObjectRequiresDocumentation = "DC0007"; + public static readonly string InternalObjectRequiresDocumentation = "DC0008"; + public static readonly string EventRequiresDocumentation = "DC0009"; + public static readonly string InternalEventRequiresDocumentation = "DC0010"; } \ No newline at end of file diff --git a/src/ALCops.LinterCop/Analyzers/ApplicationAreaRedundancy.cs b/src/ALCops.LinterCop/Analyzers/ApplicationAreaRedundancy.cs index 60f64dcb..fc6926e4 100644 --- a/src/ALCops.LinterCop/Analyzers/ApplicationAreaRedundancy.cs +++ b/src/ALCops.LinterCop/Analyzers/ApplicationAreaRedundancy.cs @@ -26,7 +26,7 @@ private void CheckDataClassificationRedundancy(SymbolAnalysisContext ctx) return; IApplicationObjectTypeSymbol? applicationObject = control.GetContainingApplicationObjectTypeSymbol(); - if (applicationObject is not IPageTypeSymbol page || applicationObject.IsObsolete()) + if (applicationObject is not IPageTypeSymbol page) return; IPropertySymbol? controlApplicationArea = control.GetProperty(EnumProvider.PropertyKind.ApplicationArea); From a96e9df5d4bb237813bcf4d78fcd281c21970350 Mon Sep 17 00:00:00 2001 From: Carsten Scholling Date: Wed, 17 Jun 2026 14:22:56 +0200 Subject: [PATCH 3/4] Decorator fix --- .../IntegrationEventHasDiagnostic/BusinessEvent.al | 2 +- .../IntegrationEventHasDiagnostic/IntegrationEvent.al | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEvent.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEvent.al index 1fada342..d0c4fa9d 100644 --- a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEvent.al +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/BusinessEvent.al @@ -1,6 +1,6 @@ codeunit 50100 MyCodeunit { - [BusinessEvent(false, false)] + [BusinessEvent(false)] local procedure [|OnBeforeTheWorldGoesLight|]() begin end; diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEvent.al b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEvent.al index 937219bc..1bd609af 100644 --- a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEvent.al +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/IntegrationEventHasDiagnostic/IntegrationEvent.al @@ -1,6 +1,6 @@ codeunit 50100 MyCodeunit { - [IntegrationEvent(false)] + [IntegrationEvent(false, false)] local procedure [|OnBeforeTheWorldGoesLight|]() begin end; From 41a62725e342aabd4d21a9b005a021e21323fa75 Mon Sep 17 00:00:00 2001 From: Carsten Scholling Date: Fri, 19 Jun 2026 04:58:26 +0200 Subject: [PATCH 4/4] #341 DC0006, DC0008, DC0010 isEnabledByDefault = false, Added ruleset --- .../Extensions/SyntaxNodeExtensions.cs | 12 ---- .../ObjectRequiresDocumentation.cs | 9 ++- .../ObjectRequiresDocumentation.ruleset.json | 10 +++ .../ProcedureRequiresDocumentation.cs | 9 ++- ...rocedureRequiresDocumentation.ruleset.json | 14 ++++ .../Analyzers/ObjectRequiresDocumentation.cs | 4 +- .../ProcedureRequiresDocumentation.cs | 69 +++++++++---------- .../DiagnosticDescriptors.cs | 14 ++-- 8 files changed, 78 insertions(+), 63 deletions(-) create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.ruleset.json create mode 100644 src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.ruleset.json diff --git a/src/ALCops.Common/Extensions/SyntaxNodeExtensions.cs b/src/ALCops.Common/Extensions/SyntaxNodeExtensions.cs index 3007fd61..6fe2b951 100644 --- a/src/ALCops.Common/Extensions/SyntaxNodeExtensions.cs +++ b/src/ALCops.Common/Extensions/SyntaxNodeExtensions.cs @@ -6,18 +6,6 @@ namespace ALCops.Common.Extensions; public static class SyntaxNodeExtensions { - /// - /// Checks whether the method is an IntegrationEvent or BusinessEvent. - /// - public static bool IsIntegrationOrBusinessEvent(this MethodDeclarationSyntax methodSyntax) => - methodSyntax.Attributes.Any(a => (a.Name.Identifier.ToString() == "IntegrationEvent") || (a.Name.Identifier.ToString() == "BusinessEvent")); - - /// - /// Checks whether the method is an InternalEvent. - /// - public static bool IsInternalEvent(this MethodDeclarationSyntax methodSyntax) => - methodSyntax.Attributes.Any(a => a.Name.Identifier.ToString() == "InternalEvent"); - public static int? GetIntegerPropertyValue(this LabelPropertyValueSyntax? labelProperty, IdentifierProperty property) => labelProperty?.Value.GetIntegerPropertyValue(property); diff --git a/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.cs b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.cs index 0473f13c..5f67cd53 100644 --- a/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.cs +++ b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.cs @@ -10,12 +10,17 @@ public class ObjectRequiresDocumentation : NavCodeAnalysisBase [SetUp] public void Setup() { - _fixture = RoslynFixtureFactory.Create(); - _testCasePath = Path.Combine( Directory.GetParent( Environment.CurrentDirectory)!.Parent!.Parent!.FullName, Path.Combine("Rules", nameof(ObjectRequiresDocumentation))); + + _fixture = RoslynFixtureFactory.Create( + // Inject a ruleset to enable testing for rules, that are not enabled by default (isEnabledByDefault: false). + new AnalyzerTestFixtureConfig + { + RuleSetPath = Path.Combine(_testCasePath, $"{nameof(ObjectRequiresDocumentation)}.ruleset.json") + }); } [Test] diff --git a/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.ruleset.json b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.ruleset.json new file mode 100644 index 00000000..ddf7f1c5 --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ObjectRequiresDocumentation/ObjectRequiresDocumentation.ruleset.json @@ -0,0 +1,10 @@ +{ + "name": "Enable DC0008", + "description": "Enables the default-disabled ObjectRequiresDocumentation.ruleset rule so it can be tested.", + "rules": [ + { + "id": "DC0008", + "action": "Info" + } + ] +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs index 8b7606c6..675f7759 100644 --- a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.cs @@ -10,12 +10,17 @@ public class ProcedureRequiresDocumentation : NavCodeAnalysisBase [SetUp] public void Setup() { - _fixture = RoslynFixtureFactory.Create(); - _testCasePath = Path.Combine( Directory.GetParent( Environment.CurrentDirectory)!.Parent!.Parent!.FullName, Path.Combine("Rules", nameof(ProcedureRequiresDocumentation))); + + _fixture = RoslynFixtureFactory.Create( + // Inject a ruleset to enable testing for rules, that are not enabled by default (isEnabledByDefault: false). + new AnalyzerTestFixtureConfig + { + RuleSetPath = Path.Combine(_testCasePath, $"{nameof(ProcedureRequiresDocumentation)}.ruleset.json") + }); } [Test] diff --git a/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.ruleset.json b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.ruleset.json new file mode 100644 index 00000000..a6a57dfb --- /dev/null +++ b/src/ALCops.DocumentationCop.Test/Rules/ProcedureRequiresDocumentation/ProcedureRequiresDocumentation.ruleset.json @@ -0,0 +1,14 @@ +{ + "name": "Enable DC0006, DC0010", + "description": "Enables the default-disabled ProcedureRequiresDocumentation.ruleset rule so it can be tested.", + "rules": [ + { + "id": "DC0006", + "action": "Info" + }, + { + "id": "DC0010", + "action": "Info" + } + ] +} \ No newline at end of file diff --git a/src/ALCops.DocumentationCop/Analyzers/ObjectRequiresDocumentation.cs b/src/ALCops.DocumentationCop/Analyzers/ObjectRequiresDocumentation.cs index aafbbfc3..90847bd3 100644 --- a/src/ALCops.DocumentationCop/Analyzers/ObjectRequiresDocumentation.cs +++ b/src/ALCops.DocumentationCop/Analyzers/ObjectRequiresDocumentation.cs @@ -31,9 +31,9 @@ public override void Initialize(AnalysisContext context) private void CheckObjectDocumentation(SymbolAnalysisContext ctx) { - if (ctx.Compilation.FileSystem is null) + if (ctx.IsObsolete()) { - return; + return; } if (ctx.Symbol is not IApplicationObjectTypeSymbol appObjectTypeSymbol) diff --git a/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs b/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs index 95cfdcd4..6a5f7b66 100644 --- a/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs +++ b/src/ALCops.DocumentationCop/Analyzers/ProcedureRequiresDocumentation.cs @@ -28,9 +28,9 @@ private void AnalyzeProcedures(SyntaxNodeAnalysisContext ctx) if (ctx.IsObsolete() || ctx.Node is not MethodDeclarationSyntax method) return; - var containingObject = ctx.ContainingSymbol.GetContainingObjectTypeSymbol(); + var containingObject = ctx.ContainingSymbol.GetContainingApplicationObjectTypeSymbol(); - if (IsTestCodeunit(containingObject)) + if (containingObject.IsTestCodeunit()) return; if (HasXmlDocumentation(method)) @@ -38,46 +38,46 @@ private void AnalyzeProcedures(SyntaxNodeAnalysisContext ctx) var accessibilityToken = method.ProcedureKeyword.GetPreviousToken(); - if (method.IsIntegrationOrBusinessEvent()) + if (ctx.ContainingSymbol is IMethodSymbol methodSymbol && (methodSymbol is not null)) { - var loc = method.Name.GetLocation(); - var id = method.Name.Identifier.ToString(); - - ctx.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.EventRequiresDocumentation, - loc, - id)); - } - - else if (method.IsInternalEvent()) - { - ctx.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.InternalEventRequiresDocumentation, - method.Name.GetLocation(), - method.Name.Identifier.ToString())); - } - - else - { - if (accessibilityToken.Kind == EnumProvider.SyntaxKind.LocalKeyword) + if (methodSymbol.IsIntegrationOrBusinessEvent()) { - return; + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.EventRequiresDocumentation, + method.Name.GetLocation(), + method.Name.Identifier.ToString())); } - if ((accessibilityToken.Kind == EnumProvider.SyntaxKind.InternalKeyword) || - (containingObject.DeclaredAccessibility == EnumProvider.Accessibility.Internal)) + else if (methodSymbol.IsInternalEvent()) { ctx.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.InternalProcedureRequiresDocumentation, + DiagnosticDescriptors.InternalEventRequiresDocumentation, method.Name.GetLocation(), method.Name.Identifier.ToString())); } + else { - ctx.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.PublicProcedureRequiresDocumentation, - method.Name.GetLocation(), - method.Name.Identifier.ToString())); + if (accessibilityToken.Kind == EnumProvider.SyntaxKind.LocalKeyword) + { + return; + } + + if ((accessibilityToken.Kind == EnumProvider.SyntaxKind.InternalKeyword) || + (containingObject?.DeclaredAccessibility == EnumProvider.Accessibility.Internal)) + { + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.InternalProcedureRequiresDocumentation, + method.Name.GetLocation(), + method.Name.Identifier.ToString())); + } + else + { + ctx.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.PublicProcedureRequiresDocumentation, + method.Name.GetLocation(), + method.Name.Identifier.ToString())); + } } } } @@ -90,11 +90,4 @@ private static bool HasXmlDocumentation(MethodDeclarationSyntax method) t.Kind == EnumProvider.SyntaxKind.SingleLineDocumentationCommentTrivia || t.Kind == EnumProvider.SyntaxKind.MultiLineDocumentationCommentTrivia); } - - private static bool IsTestCodeunit(IObjectTypeSymbol symbol) - { - var subtype = symbol.GetEnumPropertyValue(EnumProvider.PropertyKind.Subtype); - - return subtype is not null && subtype == EnumProvider.CodeunitSubtypeKind.Test; - } } \ No newline at end of file diff --git a/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs b/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs index 4b66bc02..514071da 100644 --- a/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs +++ b/src/ALCops.DocumentationCop/DiagnosticDescriptors.cs @@ -60,8 +60,8 @@ public static class DiagnosticDescriptors title: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationTitle, messageFormat: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationMessageFormat, category: Category.Design, - defaultSeverity: DiagnosticSeverity.Hidden, - isEnabledByDefault: true, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: false, description: DocumentationCopAnalyzers.InternalProcedureRequiresDocumentationDescription, helpLinkUri: GetHelpUri(DiagnosticIds.InternalProcedureRequiresDocumentation)); @@ -80,8 +80,8 @@ public static class DiagnosticDescriptors title: DocumentationCopAnalyzers.InternalObjectRequiresDocumentationTitle, messageFormat: DocumentationCopAnalyzers.InternalObjectRequiresDocumentationMessageFormat, category: Category.Design, - defaultSeverity: DiagnosticSeverity.Hidden, - isEnabledByDefault: true, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: false, description: DocumentationCopAnalyzers.InternalObjectRequiresDocumentationDescription, helpLinkUri: GetHelpUri(DiagnosticIds.InternalObjectRequiresDocumentation)); @@ -90,7 +90,7 @@ public static class DiagnosticDescriptors title: DocumentationCopAnalyzers.EventRequiresDocumentationTitle, messageFormat: DocumentationCopAnalyzers.EventRequiresDocumentationMessageFormat, category: Category.Design, - defaultSeverity: DiagnosticSeverity.Hidden, + defaultSeverity: DiagnosticSeverity.Info, isEnabledByDefault: true, description: DocumentationCopAnalyzers.EventRequiresDocumentationDescription, helpLinkUri: GetHelpUri(DiagnosticIds.EventRequiresDocumentation)); @@ -100,8 +100,8 @@ public static class DiagnosticDescriptors title: DocumentationCopAnalyzers.InternalEventRequiresDocumentationTitle, messageFormat: DocumentationCopAnalyzers.InternalEventRequiresDocumentationMessageFormat, category: Category.Design, - defaultSeverity: DiagnosticSeverity.Hidden, - isEnabledByDefault: true, + defaultSeverity: DiagnosticSeverity.Info, + isEnabledByDefault: false, description: DocumentationCopAnalyzers.InternalEventRequiresDocumentationDescription, helpLinkUri: GetHelpUri(DiagnosticIds.InternalEventRequiresDocumentation));