Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,43 @@ public static bool MethodImplementsInterfaceMethod(this IApplicationObjectTypeSy
return false;
}

/// <summary>
/// Returns true if the object is a test codeunit with TestPermissions = Disabled.
/// </summary>
public static bool IsTestCodeunitWithPermissionsDisabled(this IApplicationObjectTypeSymbol? containingObject)
{
if (!containingObject.IsTestCodeunit())
{
return false;
}

var testPermissions = (containingObject as ICodeunitTypeSymbol)?.GetEnumPropertyValue<TestPermissionsKind>(EnumProvider.PropertyKind.TestPermissions);

return testPermissions is not null && testPermissions == EnumProvider.TestPermissionsKind.Disabled;
}

/// <summary>
/// Returns true if the object is a test codeunit.
/// </summary>
public static bool IsTestCodeunit(this IApplicationObjectTypeSymbol? objectSymbol)
{
if (objectSymbol is not ICodeunitTypeSymbol codeunit)
{
return false;
}

var subtype = codeunit.GetEnumPropertyValue<CodeunitSubtypeKind>(EnumProvider.PropertyKind.Subtype);

return (subtype is not null) && (subtype == EnumProvider.CodeunitSubtypeKind.Test);
}

/// <summary>
/// Returns true if the object is a codeunit with accessability internal.
/// </summary>
public static bool IsInternalCodeunit(this IApplicationObjectTypeSymbol applicationObject) =>
applicationObject.Kind == EnumProvider.SymbolKind.Codeunit &&
applicationObject.DeclaredAccessibility == EnumProvider.Accessibility.Internal;

/// <summary>
/// Gets the flattened list of all xmlport nodes (including deeply nested) for xmlport objects.
/// Uses reflection to access the internal <c>SourceXmlPortTypeSymbol.FlattenedNodes</c> property,
Expand Down
13 changes: 13 additions & 0 deletions src/ALCops.Common/Extensions/MethodSymbolInterfaceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ public static bool MethodImplementsInterfaceMethod(this IMethodSymbol methodSymb
public static bool IsHandler(this IMethodSymbol method)
=> method.GetPropertyIfExists<bool>("IsHandler");

/// <summary>
/// Checks whether the method is an IntegrationEvent or BusinessEvent.
/// </summary>
public static bool IsIntegrationOrBusinessEvent(this IMethodSymbol methodSymbol) =>
methodSymbol.Attributes.Any(attr => (attr.AttributeKind == EnumProvider.AttributeKind.IntegrationEvent) || (attr.AttributeKind == EnumProvider.AttributeKind.BusinessEvent));

/// <summary>
/// Checks whether the method is an InternalEvent.
/// </summary>
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)
Expand Down
15 changes: 0 additions & 15 deletions src/ALCops.Common/Permissions/RequiredPermissionDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,21 +125,6 @@ public static IEnumerable<RequiredPermission> GetFromXmlPortNode(ISymbol symbol,
/// </summary>
public static bool IsSystemTable(ITableTypeSymbol table) => table.Id > 2000000000;

/// <summary>
/// Returns true if the object is a test codeunit with TestPermissions = Disabled.
/// </summary>
public static bool IsTestCodeunitWithPermissionsDisabled(IApplicationObjectTypeSymbol? containingObject)
{
if (containingObject is not ICodeunitTypeSymbol codeunit)
return false;

var subtype = codeunit.GetEnumPropertyValue<CodeunitSubtypeKind>(EnumProvider.PropertyKind.Subtype);
if (subtype is null || subtype != EnumProvider.CodeunitSubtypeKind.Test)
return false;

var testPermissions = codeunit.GetEnumPropertyValue<TestPermissionsKind>(EnumProvider.PropertyKind.TestPermissions);
return testPermissions is not null && testPermissions == EnumProvider.TestPermissionsKind.Disabled;
}

private static DirectionKind ResolveXmlPortDirection(IXmlPortTypeSymbol xmlPort)
{
Expand Down
3 changes: 3 additions & 0 deletions src/ALCops.Common/Reflection/EnumProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,8 @@ public static class SymbolKind
new(() => ParseEnum<NavCodeAnalysis.SymbolKind>(nameof(NavCodeAnalysis.SymbolKind.Codeunit)));
private static readonly Lazy<NavCodeAnalysis.SymbolKind> _control =
new(() => ParseEnum<NavCodeAnalysis.SymbolKind>(nameof(NavCodeAnalysis.SymbolKind.Control)));
private static readonly Lazy<NavCodeAnalysis.SymbolKind> _controlAddIn =
new(() => ParseEnum<NavCodeAnalysis.SymbolKind>(nameof(NavCodeAnalysis.SymbolKind.ControlAddIn)));
private static readonly Lazy<NavCodeAnalysis.SymbolKind> _entitlement =
new(() => ParseEnum<NavCodeAnalysis.SymbolKind>(nameof(NavCodeAnalysis.SymbolKind.Entitlement)));
private static readonly Lazy<NavCodeAnalysis.SymbolKind> _enum =
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
codeunit 50100 [|MyCodeunit|]
{
Access = Internal;

procedure MyProcedure()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// <summary>
/// A great piece of code that solves all problems.
/// </summary>
codeunit 50100 [|MyCodeunit|]
{
Access = Internal;

procedure MyProcedure()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using RoslynTestKit;

namespace ALCops.DocumentationCop.Test
{
public class ObjectRequiresDocumentation : NavCodeAnalysisBase
{
private AnalyzerTestFixture _fixture;
private string _testCasePath;

[SetUp]
public void Setup()
{
_testCasePath = Path.Combine(
Directory.GetParent(
Environment.CurrentDirectory)!.Parent!.Parent!.FullName,
Path.Combine("Rules", nameof(ObjectRequiresDocumentation)));

_fixture = RoslynFixtureFactory.Create<Analyzers.ObjectRequiresDocumentation>(
// 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]
[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.PublicObjectRequiresDocumentation);
}

[Test]
[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.PublicObjectRequiresDocumentation);
}

[Test]
[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.InternalObjectRequiresDocumentation);
}

[Test]
[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.InternalObjectRequiresDocumentation);
}
}
}
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
codeunit 50100 [|MyCodeunit|]
{
procedure MyProcedure()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// <summary>
/// A great piece of code that solves all problems.
/// </summary>
codeunit 50100 [|MyCodeunit|]
{
procedure MyProcedure()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
codeunit 50100 MyCodeunit
{
[BusinessEvent(false)]
local procedure [|OnBeforeTheWorldGoesLight|]()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
codeunit 50100 MyCodeunit
{
// This is a comment
[BusinessEvent(false)]
local procedure [|OnBeforeTheWorldGoesLight|]()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
codeunit 50100 MyCodeunit
{
[BusinessEvent(false)]
local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text)
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
codeunit 50100 MyCodeunit
{
[IntegrationEvent(false, false)]
local procedure [|OnBeforeTheWorldGoesLight|]()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
codeunit 50100 MyCodeunit
{
// This is a comment
[IntegrationEvent(false, false)]
local procedure [|OnBeforeTheWorldGoesLight|]()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
codeunit 50100 MyCodeunit
{
[IntegrationEvent(false, false)]
local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text)
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
/// <summary>
/// Triggered just before the world goes light.
/// </summary>
[BusinessEvent(false)]
local procedure [|OnBeforeTheWorldGoesLight|]()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
codeunit 50100 MyCodeunit
{
/// <summary>
/// Triggered just before the world goes light.
/// </summary>
/// <param name="i">An integer value.</param>
/// <param name="d">A decimal value.</param>
/// <param name="returnText">A text to return.</param>
[BusinessEvent(false)]
local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text)
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
codeunit 50100 MyCodeunit
{
/// <summary>
/// Triggered just before the world goes light.
/// </summary>
[IntegrationEvent(false, false)]
local procedure [|OnBeforeTheWorldGoesLight|]()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
codeunit 50100 MyCodeunit
{
/// <summary>
/// Triggered just before the world goes light.
/// </summary>
/// <param name="i">An integer value.</param>
/// <param name="d">A decimal value.</param>
/// <param name="returnText">A text to return.</param>
[IntegrationEvent(false, false)]
local procedure [|OnBeforeTheWorldGoesLight|](i: Integer; d: Decimal; var returnText: Text)
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
codeunit 50100 MyCodeunit
{
[InternalEvent(false, false)]
local procedure [|OnBeforeTheWorldGoesLight|]()
begin
end;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
codeunit 50100 MyCodeunit
{
// This is a comment
[InternalEvent(false, false)]
local procedure [|OnBeforeTheWorldGoesLight|]()
begin
end;
}
Loading
Loading