Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge branch 'prerelease' into master #355

Merged
merged 24 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4ca618a
Merge branch 'master' into prerelease
Arthurvdv Nov 14, 2023
6e89c5b
Improve LC0027 on input parameters
Arthurvdv Nov 15, 2023
1b48820
Improve rule0027
Arthurvdv Nov 16, 2023
ad44eb0
Raise rule on passing zero as PageID param
Arthurvdv Nov 16, 2023
3505c19
Rename description of LC0027
Arthurvdv Nov 16, 2023
72df086
Merge pull request #341 from StefanMaron/ImproveRule0027OnParams
Arthurvdv Nov 16, 2023
53f4c9c
Add LC0030 to documentation
Arthurvdv Nov 16, 2023
7e68513
Merge pull request #342 from StefanMaron/DocumentationOnRule0030
Arthurvdv Nov 16, 2023
c081312
New rule LC0031 - Record Instance Isolation Level
Arthurvdv Nov 16, 2023
217a583
Merge branch 'prerelease' into Rule0031RecordInstanceIsolationLevel
Arthurvdv Nov 16, 2023
0c19ee5
Merge pull request #344 from StefanMaron/Rule0031RecordInstanceIsolat…
Arthurvdv Nov 16, 2023
4bee790
ReadIsolation support runtime 11.0 or greater
Arthurvdv Nov 16, 2023
e02e535
Documentation
Arthurvdv Nov 16, 2023
586c546
Merge pull request #345 from StefanMaron/Rule0031RecordInstanceIsolat…
Arthurvdv Nov 16, 2023
10c53a2
New rule LC0032 Clear on Single Instance codeunits
Arthurvdv Nov 17, 2023
97d20b2
Merge branch 'prerelease' into Rule0032ClearCodeunitSingleInstance
Arthurvdv Nov 17, 2023
63e9bb8
Fix merge typos
Arthurvdv Nov 17, 2023
4d7380a
Merge pull request #348 from StefanMaron/Rule0032ClearCodeunitSingleI…
Arthurvdv Nov 17, 2023
02e45a8
Fix typo in LinterCopAnalyzers.resx
Arthurvdv Nov 17, 2023
4da336d
Resolve NullReferenceException
Arthurvdv Nov 20, 2023
d1c61a5
Resolve NullReferenceException
Arthurvdv Nov 20, 2023
24b5192
Resolve NullReferenceException
Arthurvdv Nov 20, 2023
e38c7ad
Rule LC0032 should not be raised when codeunit is used in itself
Arthurvdv Nov 20, 2023
89ae84c
Merge pull request #354 from StefanMaron/WarningShouldNotBeRaisedWhen…
Arthurvdv Nov 20, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 1 addition & 14 deletions Design/Rule0021BuiltInMethodImplementThroughCodeunit.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design
{
[DiagnosticAnalyzer]
public class Rule0021BuiltInMethodImplementThroughCodeunit : DiagnosticAnalyzer
public class BuiltInMethodImplementThroughCodeunit : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create<DiagnosticDescriptor>(
DiagnosticDescriptors.Rule0021ConfirmImplementConfirmManagement,
DiagnosticDescriptors.Rule0022GlobalLanguageImplementTranslationHelper,
DiagnosticDescriptors.Rule0027RunPageImplementPageManagement,
DiagnosticDescriptors.Rule0000ErrorInRule);

public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.CheckBuiltInMethod), OperationKind.InvocationExpression);
Expand All @@ -25,16 +22,6 @@ private void CheckBuiltInMethod(OperationAnalysisContext ctx)
IInvocationExpression operation = (IInvocationExpression)ctx.Operation;
if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return;

if (operation.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.Page && operation.Arguments.Count() > 1)
{
if (operation.TargetMethod.ReturnValueSymbol.ReturnType.NavTypeKind == NavTypeKind.Action) return; // Page Management Codeunit doesn't support returntype Action
if (operation.Arguments[0].Syntax.GetIdentifierOrLiteralValue() == "0") return; // Allow zero as input for Page.Run(0, <recordVar>)
if (operation.Arguments[0].Syntax.IsKind(SyntaxKind.IdentifierName)) return; // In case the PageID is set by a field from a (setup) record, do not raise diagnostic
if (operation.TargetMethod.Name.ToUpper() == "ENQUEUEBACKGROUNDTASK") return; // do not execute on CurrPage.EnqueueBackgroundTask
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0027RunPageImplementPageManagement, ctx.Operation.Syntax.GetLocation()));
return;
}

switch (operation.TargetMethod.Name.ToUpper())
{
case "CONFIRM":
Expand Down
101 changes: 101 additions & 0 deletions Design/Rule0027RunPageImplementPageManagement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design
{
[DiagnosticAnalyzer]
public class Rule0027RunPageImplementPageManagement : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create<DiagnosticDescriptor>(DiagnosticDescriptors.Rule0027RunPageImplementPageManagement);

public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.CheckRunPageImplementPageManagement), OperationKind.InvocationExpression);

private void CheckRunPageImplementPageManagement(OperationAnalysisContext ctx)
{
if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return;
if (ctx.ContainingSymbol.IsObsoletePending || ctx.ContainingSymbol.IsObsoleteRemoved) return;

IInvocationExpression operation = (IInvocationExpression)ctx.Operation;
if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return;

if (operation.TargetMethod.ContainingType.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Page) return;
if (operation.Arguments.Count() < 2) return;

// do not execute on CurrPage.EnqueueBackgroundTask
if (SemanticFacts.IsSameName(operation.TargetMethod.Name, "EnqueueBackgroundTask")) return;

// Page Management Codeunit doesn't support returntype Action
if (operation.TargetMethod.ReturnValueSymbol.ReturnType.GetNavTypeKindSafe() == NavTypeKind.Action) return;

switch (operation.Arguments[0].Syntax.Kind)
{
case SyntaxKind.LiteralExpression:
if (operation.Arguments[0].Syntax.GetIdentifierOrLiteralValue() == "0")
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0027RunPageImplementPageManagement, ctx.Operation.Syntax.GetLocation()));
break;

case SyntaxKind.OptionAccessExpression:
if (IsSupportedRecord(((IConversionExpression)operation.Arguments[1].Value).Operand))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0027RunPageImplementPageManagement, ctx.Operation.Syntax.GetLocation()));
break;

default:
return;
}
}

private static bool IsSupportedRecord(IOperation operation)
{
IRecordTypeSymbol recordTypeSymbol = null;

if (operation.Kind == OperationKind.GlobalReferenceExpression || operation.Kind == OperationKind.LocalReferenceExpression)
recordTypeSymbol = (IRecordTypeSymbol)operation.GetSymbol().GetTypeSymbol();

if (operation.Kind == OperationKind.InvocationExpression)
recordTypeSymbol = (IRecordTypeSymbol)operation.Type.GetTypeSymbol();

if (recordTypeSymbol == null || recordTypeSymbol.Temporary) return false;

if (GetSupportedRecords().ContainsKey(recordTypeSymbol.Id))
return SemanticFacts.IsSameName(recordTypeSymbol.Name, GetSupportedRecords()[recordTypeSymbol.Id]);

return false;
}

private static Dictionary<int, string> GetSupportedRecords()
{
Dictionary<int, string> SupportedRecords = new Dictionary<int, string>
{
{ 36, "Sales Header" },
{ 38, "Purchase Header" },
{ 79, "Company Information" },
{ 80, "Gen. Journal Template" },
{ 81, "Gen. Journal Line" },
{ 91, "User Setup" },
{ 98, "General Ledger Setup" },
{ 112, "Sales Invoice Header" },
{ 131, "Incoming Documents Setup" },
{ 207, "Res. Journal Line" },
{ 210, "Job Journal Line" },
{ 232, "Gen. Journal Batch" },
{ 312, "Purchases & Payables Setup" },
{ 454, "Approval Entry" },
{ 843, "Cash Flow Setup" },
{ 1251, "Text-to-Account Mapping" },
{ 1275, "Doc. Exch. Service Setup" },
{ 5107, "Sales Header Archive" },
{ 5109, "Purchase Header Archive" },
{ 5200, "Employee" },
{ 5405, "Production Order" },
{ 5900, "Service Header" },
{ 5965, "Service Contract Header" },
{ 7152, "Item Analysis View" },
{ 2000000120, "User" }
};
return SupportedRecords;
}
}
}
31 changes: 31 additions & 0 deletions Design/Rule0031RecordInstanceIsolationLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Microsoft.Dynamics.Nav.Analyzers.Common.AppSourceCopConfiguration;
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design
{
[DiagnosticAnalyzer]
public class Rule0031RecordInstanceIsolationLevel : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create<DiagnosticDescriptor>(DiagnosticDescriptors.Rule0031RecordInstanceIsolationLevel);

public override void Initialize(AnalysisContext context) => context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.CheckLockTable), OperationKind.InvocationExpression);

private void CheckLockTable(OperationAnalysisContext ctx)
{
// ReadIsolation is supported from runtime versions 11.0 or greater.
var manifest = AppSourceCopConfigurationProvider.GetManifest(ctx.Compilation);
if (manifest.Runtime < RuntimeVersion.Spring2023) return;

if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return;
if (ctx.ContainingSymbol.IsObsoletePending || ctx.ContainingSymbol.IsObsoleteRemoved) return;

IInvocationExpression operation = (IInvocationExpression)ctx.Operation;
if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return;

if (SemanticFacts.IsSameName(operation.TargetMethod.Name, "LockTable"))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0031RecordInstanceIsolationLevel, ctx.Operation.Syntax.GetLocation()));
}
}
}
86 changes: 86 additions & 0 deletions Design/Rule0032ClearCodeunitSingleInstance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Symbols;
using System.Collections.Immutable;

namespace BusinessCentral.LinterCop.Design
{
[DiagnosticAnalyzer]
public class Rule0032ClearCodeunitSingleInstance : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create<DiagnosticDescriptor>(DiagnosticDescriptors.Rule0032ClearCodeunitSingleInstance);

public override void Initialize(AnalysisContext context)
{
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.ClearCodeunit), OperationKind.InvocationExpression);
context.RegisterOperationAction(new Action<OperationAnalysisContext>(this.ClearAllCodeunit), OperationKind.InvocationExpression);
}

private void ClearCodeunit(OperationAnalysisContext ctx)
{
if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return;
if (ctx.ContainingSymbol.IsObsoletePending || ctx.ContainingSymbol.IsObsoleteRemoved) return;

IInvocationExpression operation = (IInvocationExpression)ctx.Operation;
if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return;

if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "Clear")) return;
if (operation.Arguments.Count() < 1) return;

IOperation operand = ((IConversionExpression)operation.Arguments[0].Value).Operand;
if (operand.GetSymbol().GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Codeunit) return;

if (IsSingleInstanceCodeunitWithGlobalVars((ICodeunitTypeSymbol)operand.GetSymbol().GetTypeSymbol()))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0032ClearCodeunitSingleInstance, ctx.Operation.Syntax.GetLocation(), new Object[] { operand.GetSymbol().Name, operand.GetSymbol().GetTypeSymbol().Name }));
}

private void ClearAllCodeunit(OperationAnalysisContext ctx)
{
if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || ctx.ContainingSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return;
if (ctx.ContainingSymbol.IsObsoletePending || ctx.ContainingSymbol.IsObsoleteRemoved) return;

if (ctx.ContainingSymbol.GetContainingObjectTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Codeunit) return;

IInvocationExpression operation = (IInvocationExpression)ctx.Operation;
if (operation.TargetMethod.MethodKind != MethodKind.BuiltInMethod) return;
if (!SemanticFacts.IsSameName(operation.TargetMethod.Name, "ClearAll")) return;

IEnumerable<ISymbol> localVariables = ((IMethodSymbol)ctx.ContainingSymbol.OriginalDefinition).LocalVariables
.Where(var => var.OriginalDefinition.GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.Codeunit)
.Where(var => var.OriginalDefinition.GetTypeSymbol().OriginalDefinition != ctx.ContainingSymbol.GetContainingObjectTypeSymbol().OriginalDefinition);
IEnumerable<ISymbol> globalVariables = ctx.ContainingSymbol.GetContainingObjectTypeSymbol()
.GetMembers()
.Where(members => members.Kind == SymbolKind.GlobalVariable)
.Where(var => var.OriginalDefinition.GetTypeSymbol().GetNavTypeKindSafe() == NavTypeKind.Codeunit)
.Where(var => var.OriginalDefinition.GetTypeSymbol().OriginalDefinition != ctx.ContainingSymbol.GetContainingObjectTypeSymbol().OriginalDefinition);

if (HasSingleInstanceCodeunitWithGlobalVars(localVariables, out ISymbol codeunit) || HasSingleInstanceCodeunitWithGlobalVars(globalVariables, out codeunit))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0032ClearCodeunitSingleInstance, ctx.Operation.Syntax.GetLocation(), new Object[] { codeunit.Name, codeunit.GetTypeSymbol().Name }));
}

private static bool HasSingleInstanceCodeunitWithGlobalVars(IEnumerable<ISymbol> variables, out ISymbol codeunit)
{
foreach (ISymbol variable in variables.Where(var => var.OriginalDefinition.ContainingType.GetNavTypeKindSafe() == NavTypeKind.Codeunit))
if (IsSingleInstanceCodeunitWithGlobalVars((ICodeunitTypeSymbol)variable.OriginalDefinition.GetTypeSymbol()))
{
codeunit = variable;
return true;
}

codeunit = null;
return false;
}

private static bool IsSingleInstanceCodeunitWithGlobalVars(ICodeunitTypeSymbol codeunitTypeSymbol)
{
IPropertySymbol singleInstanceProperty = codeunitTypeSymbol.GetProperty(PropertyKind.SingleInstance);
if (singleInstanceProperty == null || !(bool)singleInstanceProperty.Value) return false;

var globalVariables = codeunitTypeSymbol.GetMembers().Where(members => members.Kind == SymbolKind.GlobalVariable);
var globalVariablesNonRecordTypes = globalVariables.Where(vars => vars.GetTypeSymbol().GetNavTypeKindSafe() != NavTypeKind.Record);

bool globalVariablesExists = globalVariablesNonRecordTypes.Count() != 0;
return globalVariablesExists;
}
}
}
17 changes: 16 additions & 1 deletion LinterCop.ruleset.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
{
"id": "LC0027",
"action": "Info",
"justification": "Page.Run(Modal) must be implemented through the Page Management codeunit from the Base Application."
"justification": "Utilize the Page Management codeunit for launching page."
},
{
"id": "LC0028",
Expand All @@ -146,6 +146,21 @@
"id": "LC0029",
"action": "Info",
"justification": "Use CompareDateTime method in Type Helper codeunit for DateTime variable comparisons."
},
{
"id": "LC0030",
"action": "Info",
"justification": "Set Access property to Internal for Install/Upgrade codeunits."
},
{
"id": "LC0031",
"action": "Info",
"justification": "Set ReadIsolation property instead of LockTable method."
},
{
"id": "LC0032",
"action": "Warning",
"justification": "Clear(All) does not affect or change values for global variables in single instance codeunits."
}
]
}
2 changes: 2 additions & 0 deletions LinterCopAnalyzers.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ public static class DiagnosticDescriptors
public static readonly DiagnosticDescriptor Rule0028CodeNavigabilityOnEventSubscribers = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0028", (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0028CodeNavigabilityOnEventSubscribersDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0028");
public static readonly DiagnosticDescriptor Rule0029CompareDateTimeThroughCodeunit = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0029", (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0029CompareDateTimeThroughCodeunitDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0029");
public static readonly DiagnosticDescriptor Rule0030AccessInternalForInstallAndUpgradeCodeunits = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0030", (LocalizableString)new LocalizableResourceString("Rule0030AccessInternalForInstallAndUpgradeCodeunitsTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0030AccessInternalForInstallAndUpgradeCodeunitsFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0030AccessInternalForInstallAndUpgradeCodeunitsDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0030");
public static readonly DiagnosticDescriptor Rule0031RecordInstanceIsolationLevel = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0031", (LocalizableString)new LocalizableResourceString("Rule0031RecordInstanceIsolationLevelTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0031RecordInstanceIsolationLevelFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, true, (LocalizableString)new LocalizableResourceString("Rule0031RecordInstanceIsolationLevelDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0031");
public static readonly DiagnosticDescriptor Rule0032ClearCodeunitSingleInstance = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0032", (LocalizableString)new LocalizableResourceString("Rule0032ClearCodeunitSingleInstanceTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0032ClearCodeunitSingleInstanceFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Warning, true, (LocalizableString)new LocalizableResourceString("Rule0032ClearCodeunitSingleInstanceDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "https://github.com/StefanMaron/BusinessCentral.LinterCop/wiki/LC0032");
}
}
Loading
Loading