Skip to content

Commit

Permalink
Implemented code metrics Rule0009 #48
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanMaron committed Sep 29, 2021
1 parent 3794428 commit 79674d3
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 1 deletion.
1 change: 1 addition & 0 deletions BusinessCentral.LinterCop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
<ItemGroup>
<Compile Include="Design\Rule0001FlowFieldsShouldNotBeEditable.cs" />
<Compile Include="Design\Rule0008NoFilterOperatorsInSetRange.cs" />
<Compile Include="Design\Rule0009CodeMetrics.cs" />
<Compile Include="Design\Rule0007DataPerCompanyShouldAlwaysBeSet.cs" />
<Compile Include="Design\Rule0006FieldNotAutoIncrementInTemporaryTable.cs" />
<Compile Include="Design\Rule0005VariableCasingShouldNotDIfferFromDeclaration.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeCop.Utilities;
using System;
using System.Collections.Immutable;
using System.Collections.Generic;
Expand Down
74 changes: 74 additions & 0 deletions Design/Rule0009CodeMetrics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Microsoft.Dynamics.Nav.CodeAnalysis;
using Microsoft.Dynamics.Nav.CodeAnalysis.Diagnostics;
using Microsoft.Dynamics.Nav.CodeAnalysis.Syntax;
using System;
using System.Collections.Immutable;
using System.Linq;

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

public override void Initialize(AnalysisContext context)
=> context.RegisterCodeBlockAction(new Action<CodeBlockAnalysisContext>(this.CheckforMissionDataPerCompanyOnTables));

private void CheckforMissionDataPerCompanyOnTables(CodeBlockAnalysisContext context)
{
int cyclomaticComplexety = GetCyclomaticComplexety(context.CodeBlock);
double HalsteadVolume = GetHalsteadVolume(context.CodeBlock, ref context, cyclomaticComplexety);

context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0009CodeMetricsInfo, context.OwningSymbol.GetLocation(), new object[] { cyclomaticComplexety, Math.Round(HalsteadVolume)}));
}

private static double GetHalsteadVolume(SyntaxNode CodeBlock, ref CodeBlockAnalysisContext context,int cyclomaticComplexety)
{
var Syntax = CodeBlock.DescendantNodesAndTokens(e => true).ToList();
var methodBody = Syntax.Find(node => node.Kind == SyntaxKind.Block && (node.Parent.Kind == SyntaxKind.MethodDeclaration || node.Parent.Kind == SyntaxKind.TriggerDeclaration));

var OperandKinds = new object[] { SyntaxKind.IdentifierToken, SyntaxKind.Int32LiteralToken, SyntaxKind.StringLiteralToken, SyntaxKind.BooleanLiteralValue, SyntaxKind.TrueKeyword, SyntaxKind.FalseKeyword };
var OperatorKinds = Enum.GetValues(typeof(SyntaxKind)).Cast<SyntaxKind>().Where(value => (value.ToString().Contains("Keyword") || value.ToString().Contains("Token")) && !OperandKinds.Contains(value)).ToList();
var TriviaKinds = Enum.GetValues(typeof(SyntaxKind)).Cast<SyntaxKind>().Where(value => (value.ToString().Contains("Trivia"))).ToList();

var triviaLines = methodBody.AsNode().DescendantTrivia(e => true, true).Where(node => node.Kind == SyntaxKind.EndOfLineTrivia);
triviaLines = triviaLines.Where(node => node.GetLocation().GetLineSpan().StartLinePosition.Line == node.Token.GetLocation().GetLineSpan().StartLinePosition.Line);
var triviaLinesCount = triviaLines.Count() - 2;//Minus 2 for Begin end of function


var Operands = methodBody.AsNode().DescendantNodesAndTokens(e => true).Where(node => OperandKinds.Contains(node.Kind));
var Operators = methodBody.AsNode().DescendantNodesAndTokens(e => true).Where(node => OperatorKinds.Contains(node.Kind));

double N = (double)(Operators.Count() + Operands.Count());
double n = (double)Operators.Distinct().Count() + Operands.Distinct().Count();
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);

}
private static int GetCyclomaticComplexety(SyntaxNode CodeBlock)
{
var Syntax = CodeBlock.DescendantNodesAndTokens(e => true).ToList();
var ComplexKinds = new object[] { SyntaxKind.IfKeyword, SyntaxKind.ElifKeyword, SyntaxKind.LogicalAndExpression, SyntaxKind.LogicalOrExpression, SyntaxKind.CaseLine, SyntaxKind.ForKeyword, SyntaxKind.ForEachKeyword, SyntaxKind.WhileKeyword, SyntaxKind.UntilKeyword };
var nodes = Syntax.Count(node =>
{
if (node.IsNode)
{
return ComplexKinds.Contains(
node.AsNode().Kind);
}
if (node.IsToken)
{
return ComplexKinds.Contains(
node.AsToken().Kind);
}
else return false;
});
return nodes;
}
}
}


1 change: 1 addition & 0 deletions LinterCopAnalyzers.Generated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public static class DiagnosticDescriptors
public static readonly DiagnosticDescriptor Rule0006FieldNotAutoIncrementInTemporaryTable = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0006", (LocalizableString)new LocalizableResourceString("Rule0006FieldNotAutoIncrementInTemporaryTableTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0006FieldNotAutoIncrementInTemporaryTableFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Error, true, (LocalizableString)new LocalizableResourceString("Rule0006FieldNotAutoIncrementInTemporaryTableDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (string)null, Array.Empty<string>());
public static readonly DiagnosticDescriptor Rule0007DataPerCompanyShouldAlwaysBeSet = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0007", (LocalizableString)new LocalizableResourceString("Rule0007DataPerCompanyShouldAlwaysBeSetTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0007DataPerCompanyShouldAlwaysBeSetFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Hidden, false, (LocalizableString)new LocalizableResourceString("Rule0007DataPerCompanyShouldAlwaysBeSetDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (string)null, Array.Empty<string>());
public static readonly DiagnosticDescriptor Rule0008NoFilterOperatorsInSetRange = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0008", (LocalizableString)new LocalizableResourceString("Rule0008NoFilterOperatorsInSetRangeTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0008NoFilterOperatorsInSetRangeFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Warning, true, (LocalizableString)new LocalizableResourceString("Rule0008NoFilterOperatorsInSetRangeDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (string)null, Array.Empty<string>());
public static readonly DiagnosticDescriptor Rule0009CodeMetricsInfo = new DiagnosticDescriptor(LinterCopAnalyzers.AnalyzerPrefix + "0009", (LocalizableString)new LocalizableResourceString("Rule0009CodeMetricsInfoTitle", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (LocalizableString)new LocalizableResourceString("Rule0009CodeMetricsInfoFormat", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), "Design", DiagnosticSeverity.Info, false, (LocalizableString)new LocalizableResourceString("Rule0009CodeMetricsInfoDescription", LinterCopAnalyzers.ResourceManager, typeof(LinterCopAnalyzers)), (string)null, Array.Empty<string>());
}
}
9 changes: 9 additions & 0 deletions LinterCopAnalyzers.resx
Original file line number Diff line number Diff line change
Expand Up @@ -192,4 +192,13 @@
<data name="Rule0008NoFilterOperatorsInSetRangeTitle" xml:space="preserve">
<value>Filter operators should not be used in SetRange. Use SetFilter instead.</value>
</data>
<data name="Rule0009CodeMetricsInfoDescription" xml:space="preserve">
<value>Cyclomatic complexity and Maintainability index</value>
</data>
<data name="Rule0009CodeMetricsInfoFormat" xml:space="preserve">
<value>Cyclomatic complexity: {0}, Maintainability index: {1}</value>
</data>
<data name="Rule0009CodeMetricsInfoTitle" xml:space="preserve">
<value>Cyclomatic complexity and Maintainability index</value>
</data>
</root>
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Be aware tho, the `BusinessCentral.LinterCop.dll` needs to be placed in a folder
|LC0006|Fields with property "AutoIncrement" cannot be used in temporary table (TableType = Temporary).|Error|
|LC0007|Every table needs to specify "DataPerCompany". Either true or false|Disabled|
|LC0008|Filter operators should not be used in SetRange.|Warning|
|LC0009|Show info message about code metrics for each function or trigger|Disabled|

## Can I disable certain rules?

Expand Down

0 comments on commit 79674d3

Please sign in to comment.