Skip to content

Commit

Permalink
Merge branch 'master' into LC0016OnAPIObjects
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanMaron authored Jan 16, 2023
2 parents f65fe1c + 74be732 commit fb6a071
Show file tree
Hide file tree
Showing 19 changed files with 351 additions and 128 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
/Resources/Microsoft.Dynamics.Nav.Analyzers.Common.dll
/Resources/Microsoft.CodeAnalysis.dll
/packages/System.Composition.AttributedModel.5.0.1
.vscode/
.vscode/**
!.vscode/extensions.json
!.vscode/settings.json
5 changes: 5 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"recommendations": [
"streetsidesoftware.code-spell-checker"
]
}
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"cSpell.words": [
"cyclomatic",
"Halstead",
"Newtonsoft"
]
}
8 changes: 2 additions & 6 deletions BusinessCentral.LinterCop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@
<HintPath>ms-dynamics-smb.al-latest/extension/bin/Analyzers/Microsoft.Dynamics.Nav.CodeAnalysis.Workspaces.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.Dynamics.Nav.Analyzers.Common">
<SpecificVersion>False</SpecificVersion>
<HintPath>ms-dynamics-smb.al-latest/extension/bin/Analyzers/Microsoft.Dynamics.Nav.Analyzers.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>ms-dynamics-smb.al-latest/extension/bin/Analyzers/Newtonsoft.Json.dll</HintPath>
Expand Down Expand Up @@ -112,7 +107,8 @@
<Compile Include="Design\Rule0018NoEventsInInternalCodeunits.cs" />
<Compile Include="Design\Rule0019DataClassificationFieldEqualsTable.cs" />
<Compile Include="Design\Rule0020ApplicationAreaEqualsToPage.cs" />
<Compile Include="Design\Rule0021ConfirmImplementConfirmManagement.cs" />
<Compile Include="Design\Rule0021BuiltInMethodImplementThroughCodeunit.cs" />
<Compile Include="Design\Rule0023AlwaysSpecifyFieldgroups.cs" />
<Compile Include="Helpers\LinterSettings.cs" />
<Compile Include="LinterCopAnalyzers.Designer.cs" />
<Compile Include="LinterCopAnalyzers.Generated.cs" />
Expand Down
2 changes: 1 addition & 1 deletion Design/Rule0002CommitMustBeExplainedByComment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ private void CheckCommitForExplainingComment(OperationAnalysisContext ctx)
if (ctx.ContainingSymbol.IsObsoletePending ||ctx.ContainingSymbol.IsObsoleteRemoved) return;

IInvocationExpression operation = (IInvocationExpression)ctx.Operation;
if (operation.TargetMethod.Name.ToUpper() == "COMMIT")
if (operation.TargetMethod.Name.ToUpper() == "COMMIT" && operation.TargetMethod.MethodKind == MethodKind.BuiltInMethod)
{
foreach (SyntaxTrivia trivia in operation.Syntax.Parent.GetLeadingTrivia())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public override void Initialize(AnalysisContext context)
SymbolKind.XmlPort
});
}

private void CheckForBuiltInTypeCasingMismatch(SymbolAnalysisContext ctx)
{
foreach (var node in ctx.Symbol.DeclaringSyntaxReference.GetSyntax().DescendantNodesAndTokens().Where(n => IsValidToken(n)))
Expand Down
10 changes: 5 additions & 5 deletions Design/Rule0009CodeMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@ private void CheckCodeMetrics(CodeBlockAnalysisContext context)

LinterSettings.Create();
if (LinterSettings.instance != null)
if (cyclomaticComplexity >= LinterSettings.instance.cyclomaticComplexetyThreshold || Math.Round(HalsteadVolume) <= LinterSettings.instance.maintainablityIndexThreshold)
if (cyclomaticComplexity >= LinterSettings.instance.cyclomaticComplexityThreshold || Math.Round(HalsteadVolume) <= LinterSettings.instance.maintainabilityIndexThreshold)
{
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0010CodeMetricsWarning, context.OwningSymbol.GetLocation(), new object[] { cyclomaticComplexity, Math.Round(HalsteadVolume) }));
return;
}

if (cyclomaticComplexity >= LinterSettings.instance.cyclomaticComplexetyThreshold || Math.Round(HalsteadVolume) <= LinterSettings.instance.maintainablityIndexThreshold)
if (cyclomaticComplexity >= LinterSettings.instance.cyclomaticComplexityThreshold || Math.Round(HalsteadVolume) <= LinterSettings.instance.maintainabilityIndexThreshold)
{
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0010CodeMetricsWarning, context.OwningSymbol.GetLocation(), new object[] { LinterSettings.instance.cyclomaticComplexetyThreshold, Math.Round(HalsteadVolume) }));
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0010CodeMetricsWarning, context.OwningSymbol.GetLocation(), new object[] { LinterSettings.instance.cyclomaticComplexityThreshold, Math.Round(HalsteadVolume) }));
return;
}
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0009CodeMetricsInfo, context.OwningSymbol.GetLocation(), new object[] { cyclomaticComplexity, Math.Round(HalsteadVolume)}));
}

private static double GetHalsteadVolume(SyntaxNode CodeBlock, ref CodeBlockAnalysisContext context,int cyclomaticComplexety)
private static double GetHalsteadVolume(SyntaxNode CodeBlock, ref CodeBlockAnalysisContext context,int cyclomaticComplexity)
{
try
{
Expand All @@ -65,7 +65,7 @@ private static double GetHalsteadVolume(SyntaxNode CodeBlock, ref CodeBlockAnaly
double HalsteadVolume = N * Math.Log(n, 2);

//171−5.2lnV−0.23G−16.2lnL
return Math.Max(0, (171 - 5.2 * Math.Log(HalsteadVolume) - 0.23 * cyclomaticComplexety - 16.2 * Math.Log(triviaLinesCount)) * 100 / 171);
return Math.Max(0, (171 - 5.2 * Math.Log(HalsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.Log(triviaLinesCount)) * 100 / 171);

}
catch (System.NullReferenceException)
Expand Down
22 changes: 14 additions & 8 deletions Design/Rule0014PermissionSetCaptionLength.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,21 @@ private void CheckPermissionSetNameAndCaptionLength(SymbolAnalysisContext contex
return;

var maxLengthProperty = captionSubProperties.DescendantNodes().FirstOrDefault(e => e.ToString().StartsWith("MaxLength"));
if (captionSubProperties.ToString() != "")
{
if (Int32.TryParse(maxLengthProperty.DescendantNodes().FirstOrDefault(e => e.Kind == SyntaxKind.Int32SignedLiteralValue).ToString(), out int maxLengthValue))
if (maxLengthValue > MAXCAPTIONLENGTH)
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0014PermissionSetCaptionLength, captionProperty.GetLocation(), new object[] { MAXCAPTIONLENGTH }));
}
if (maxLengthProperty != null)
if (captionSubProperties.ToString() != "")
{
if (Int32.TryParse(maxLengthProperty.DescendantNodes().FirstOrDefault(e => e.Kind == SyntaxKind.Int32SignedLiteralValue).ToString(), out int maxLengthValue))
{
if (maxLengthValue > MAXCAPTIONLENGTH)
{
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0014PermissionSetCaptionLength, captionProperty.GetLocation(), new object[] { MAXCAPTIONLENGTH }));
}
return;
}
}
}
else
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0014PermissionSetCaptionLength, captionProperty.GetLocation(), new object[] { MAXCAPTIONLENGTH }));

context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0014PermissionSetCaptionLength, captionProperty.GetLocation(), new object[] { MAXCAPTIONLENGTH }));
}
}
}
8 changes: 7 additions & 1 deletion Design/Rule0017WriteToFlowField.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ private void CheckForWriteToFlowField(OperationAnalysisContext context)
IInvocationExpression operation = (IInvocationExpression)context.Operation;
if (operation.TargetMethod.Name == "Validate" && operation.TargetMethod.ContainingType.ToString() == "Table")
{
var FieldClass = ((IFieldAccess)((IConversionExpression)operation.Arguments[0].Value).Operand).FieldSymbol.FieldClass;
IFieldSymbol field = null;
if (operation.Arguments[0].Value.GetType().GetProperty("Operand") != null)
field = ((IFieldAccess)((IConversionExpression)operation.Arguments[0].Value).Operand).FieldSymbol;
else
field = ((IFieldAccess)operation.Arguments[0].Value).FieldSymbol;

var FieldClass = field.FieldClass;
if (FieldClass == FieldClassKind.FlowField)
if (!HasExplainingComment(context.Operation))
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0017WriteToFlowField, context.Operation.Syntax.GetLocation()));
Expand Down
50 changes: 50 additions & 0 deletions Design/Rule0021BuiltInMethodImplementThroughCodeunit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using System;
using System.Collections.Immutable;

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

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

private void CheckConfirm(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;


switch (operation.TargetMethod.Name.ToUpper())
{
case "CONFIRM":
try
{
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0021ConfirmImplementConfirmManagement, ctx.Operation.Syntax.GetLocation()));
}
catch (ArgumentOutOfRangeException)
{
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0000ErrorInRule, ctx.Operation.Syntax.GetLocation(), new Object[] { "Rule0021", "ArgumentOutOfRangeException", "at Line 29" }));
}
break;
case "GLOBALLANGUAGE":
try
{
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0022GlobalLanguageImplementTranslationHelper, ctx.Operation.Syntax.GetLocation()));
}
catch (ArgumentOutOfRangeException)
{
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0000ErrorInRule, ctx.Operation.Syntax.GetLocation(), new Object[] { "Rule0022", "ArgumentOutOfRangeException", "at Line 39" }));
}
break;
}

}
}
}
26 changes: 0 additions & 26 deletions Design/Rule0021ConfirmImplementConfirmManagement.cs

This file was deleted.

44 changes: 44 additions & 0 deletions Design/Rule0023AlwaysSpecifyFieldgroups.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Text;
using System;
using System.Collections.Immutable;
using System.Linq;

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

public override void Initialize(AnalysisContext context) => context.RegisterSymbolAction(new Action<SymbolAnalysisContext>(this.CheckFieldgroups), SymbolKind.Table);

private void CheckFieldgroups(SymbolAnalysisContext ctx)
{
if (ctx.Symbol.IsObsoletePending || ctx.Symbol.IsObsoleteRemoved) return;
try
{
ITableTypeSymbol table = (ITableTypeSymbol)ctx.Symbol;

Location FieldGroupLocation = table.GetLocation();
if (!table.Keys.IsEmpty)
{
FieldGroupLocation = table.Keys.Last().GetLocation();
var span = FieldGroupLocation.SourceSpan;
FieldGroupLocation = Location.Create(FieldGroupLocation.SourceTree, new TextSpan(span.End + 9, 1)); //Should result in the blank line right after the keys section
}

if (!table.FieldGroups.Any(item => (item.Name == "Brick")))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0023AlwaysSpecifyFieldgroups, FieldGroupLocation, "Brick", table.Name));

if (!table.FieldGroups.Any(item => (item.Name == "DropDown")))
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0023AlwaysSpecifyFieldgroups, FieldGroupLocation, "DropDown", table.Name));
}
catch (ArgumentOutOfRangeException)
{
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0000ErrorInRule, ctx.Symbol.GetLocation(), new Object[] { "Rule0023", "ArgumentOutOfRangeException", "" }));
}
}
}
}
22 changes: 17 additions & 5 deletions Helpers/LinterSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ namespace BusinessCentral.LinterCop.Helpers
{
class LinterSettings
{
public int cyclomaticComplexetyThreshold = 8;
public int maintainablityIndexThreshold = 20;
public int cyclomaticComplexityThreshold = 8;
public int maintainabilityIndexThreshold = 20;
public bool enableRule0011ForTableFields = false;
public bool enableRule0016ForApiObjects = false;
static public LinterSettings instance;
Expand All @@ -20,9 +20,12 @@ static public void Create()
StreamReader r = File.OpenText("LinterCop.json");
string json = r.ReadToEnd();
r.Close();
instance = JsonConvert.DeserializeObject<LinterSettings>(json);
if (instance == null)
instance = new LinterSettings();
instance = new LinterSettings();

InternalLinterSettings internalInstance = JsonConvert.DeserializeObject<InternalLinterSettings>(json);
instance.cyclomaticComplexityThreshold = internalInstance.cyclomaticComplexityThreshold ?? internalInstance.cyclomaticComplexetyThreshold ?? instance.cyclomaticComplexityThreshold;
instance.maintainabilityIndexThreshold = internalInstance.maintainabilityIndexThreshold ?? internalInstance.maintainablityIndexThreshold ?? instance.maintainabilityIndexThreshold;
instance.enableRule0011ForTableFields = internalInstance.enableRule0011ForTableFields;
}
catch
{
Expand All @@ -31,4 +34,13 @@ static public void Create()
}
}
}
internal class InternalLinterSettings
{
public int? cyclomaticComplexityThreshold;
public int? maintainabilityIndexThreshold;
public int? cyclomaticComplexetyThreshold; // Misspelled, deprecated
public int? maintainablityIndexThreshold; // Misspelled, deprecated
public bool enableRule0011ForTableFields = false;

}
}
4 changes: 2 additions & 2 deletions LinterCop.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"cyclomaticComplexetyThreshold": 8,
"maintainablityIndexThreshold": 20,
"cyclomaticComplexityThreshold": 8,
"maintainabilityIndexThreshold": 20,
"enableRule0011ForTableFields": false,
"enableRule0016ForApiObjects": false
}
35 changes: 35 additions & 0 deletions LinterCop.ruleset.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,41 @@
"id": "LC0015",
"action": "Warning",
"justification": "All application objects should be covered by at least one permission set in the extension."
},
{
"id": "LC0016",
"action": "Warning",
"justification": "Caption is missing. Optionally this can also be activated for fields on API objects with the setting enableRule0016ForApiObjects"
},
{
"id": "LC0017",
"action": "Warning",
"justification": "Writing to a FlowField is not common. Add a comment to explain this."
},
{
"id": "LC0018",
"action": "Info",
"justification": "Events in internal codeunits are not accessible to extensions and should therefore be avoided."
},
{
"id": "LC0019",
"action": "Info",
"justification": "If Data Classification is set on the Table. Fields do not need the same classification."
},
{
"id": "LC0020",
"action": "Info",
"justification": "If Application Area is set on the TablePage. Controls do not need the same classification."
},
{
"id": "LC0021",
"action": "Info",
"justification": "Confirm() must be implemented through the Confirm Management codeunit from the System Application."
},
{
"id": "LC0022",
"action": "Info",
"justification": "GlobalLanguage() must be implemented through the Translation Helper codeunit from the Base Application."
}
]
}
Loading

0 comments on commit fb6a071

Please sign in to comment.