Skip to content

Commit

Permalink
Merge pull request #465 from StefanMaron/development
Browse files Browse the repository at this point in the history
Optimized LC0009, LC0010
  • Loading branch information
Arthurvdv authored Dec 20, 2023
2 parents c3c3ff3 + 6e93440 commit 4391d44
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 49 deletions.
144 changes: 96 additions & 48 deletions Design/Rule0009CodeMetrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,52 @@ namespace BusinessCentral.LinterCop.Design
public class Rule0009CodeMetrics : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create<DiagnosticDescriptor>(DiagnosticDescriptors.Rule0009CodeMetricsInfo, DiagnosticDescriptors.Rule0010CodeMetricsWarning);

private static readonly HashSet<SyntaxKind> OperatorAndOperandKinds =
Enum.GetValues(typeof(SyntaxKind))
.Cast<SyntaxKind>()
.Where(value =>
(value.ToString().Contains("Keyword") ||
value.ToString().Contains("Token")) ||
IsOperandKind(value))
.ToHashSet();

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

private void CheckCodeMetrics(CodeBlockAnalysisContext context)
{
if (context.OwningSymbol.GetContainingObjectTypeSymbol().IsObsoletePending || context.OwningSymbol.GetContainingObjectTypeSymbol().IsObsoleteRemoved) return;
if (context.OwningSymbol.IsObsoletePending || context.OwningSymbol.IsObsoleteRemoved) return;
if (context.OwningSymbol.GetContainingObjectTypeSymbol().NavTypeKind == NavTypeKind.Interface || context.OwningSymbol.GetContainingObjectTypeSymbol().NavTypeKind == NavTypeKind.ControlAddIn) return;
if ((context.CodeBlock.Kind != SyntaxKind.MethodDeclaration) &&
(context.CodeBlock.Kind != SyntaxKind.TriggerDeclaration))
return;

if (context.OwningSymbol.IsObsoletePending || context.OwningSymbol.IsObsoleteRemoved)
return;

int cyclomaticComplexity = GetCyclomaticComplexity(context.CodeBlock);
double HalsteadVolume = GetHalsteadVolume(context.CodeBlock, ref context, cyclomaticComplexity);
var containingObjectTypeSymbol = context.OwningSymbol.GetContainingObjectTypeSymbol();
if (containingObjectTypeSymbol.IsObsoletePending ||
containingObjectTypeSymbol.IsObsoleteRemoved)
return;

if (containingObjectTypeSymbol.NavTypeKind == NavTypeKind.Interface ||
containingObjectTypeSymbol.NavTypeKind == NavTypeKind.ControlAddIn)
return;

LinterSettings.Create(context.SemanticModel.Compilation.FileSystem.GetDirectoryPath());
if (LinterSettings.instance != null)
if (cyclomaticComplexity >= LinterSettings.instance.cyclomaticComplexityThreshold || Math.Round(HalsteadVolume) <= LinterSettings.instance.maintainabilityIndexThreshold)
{
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0010CodeMetricsWarning, context.OwningSymbol.GetLocation(), new object[] { cyclomaticComplexity, LinterSettings.instance.cyclomaticComplexityThreshold, Math.Round(HalsteadVolume), LinterSettings.instance.maintainabilityIndexThreshold }));
return;
}
SyntaxNode bodyNode = context.CodeBlock.Kind == SyntaxKind.MethodDeclaration
? (context.CodeBlock as MethodDeclarationSyntax)?.Body
: (context.CodeBlock as TriggerDeclarationSyntax)?.Body;

if (bodyNode is null)
return;

var descendants = bodyNode.DescendantNodesAndTokens(e => true).ToArray();

int cyclomaticComplexity = GetCyclomaticComplexity(descendants);
double HalsteadVolume = GetHalsteadVolume(context, bodyNode, descendants, cyclomaticComplexity);

if (LinterSettings.instance == null)
LinterSettings.Create(context.SemanticModel.Compilation.FileSystem.GetDirectoryPath());

if (cyclomaticComplexity >= LinterSettings.instance.cyclomaticComplexityThreshold || Math.Round(HalsteadVolume) <= LinterSettings.instance.maintainabilityIndexThreshold)
{
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0010CodeMetricsWarning, context.OwningSymbol.GetLocation(), new object[] { cyclomaticComplexity, LinterSettings.instance.cyclomaticComplexityThreshold, Math.Round(HalsteadVolume), LinterSettings.instance.maintainabilityIndexThreshold }));
Expand All @@ -39,58 +64,81 @@ private void CheckCodeMetrics(CodeBlockAnalysisContext context)
context.ReportDiagnostic(Diagnostic.Create(DiagnosticDescriptors.Rule0009CodeMetricsInfo, context.OwningSymbol.GetLocation(), new object[] { cyclomaticComplexity, LinterSettings.instance.cyclomaticComplexityThreshold, Math.Round(HalsteadVolume), LinterSettings.instance.maintainabilityIndexThreshold }));
}

private static double GetHalsteadVolume(SyntaxNode CodeBlock, ref CodeBlockAnalysisContext context, int cyclomaticComplexity)
private static double GetHalsteadVolume(CodeBlockAnalysisContext context, SyntaxNode methodBodyNode,
SyntaxNodeOrToken[] descendantNodesAndTokens, int cyclomaticComplexity)
{
try
{
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));
var triviaLinesCount = methodBodyNode
.DescendantTrivia(e => true, true)
.Count(node =>
node.Kind == SyntaxKind.EndOfLineTrivia &&
node.GetLocation().GetLineSpan().StartLinePosition.Line ==
node.Token.GetLocation().GetLineSpan().StartLinePosition.Line) - 2; //Minus 2 for Begin end of function

context.CancellationToken.ThrowIfCancellationRequested();
var N = 0;
using var hashSet = PooledHashSet<SyntaxNodeOrToken>.GetInstance();
foreach (var nodeOrToken in descendantNodesAndTokens)
{
if (OperatorAndOperandKinds.Contains(nodeOrToken.Kind))
{
N++;
hashSet.Add(nodeOrToken);
}
}

double N = (double)(Operators.Count() + Operands.Count());
double n = (double)Operators.Distinct().Count() + Operands.Distinct().Count();
double HalsteadVolume = N * Math.Log(n, 2);
double HalsteadVolume = N * Math.Log(hashSet.Count, 2);

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

}
catch (System.NullReferenceException)
{
return 0.0;
}
}
private static int GetCyclomaticComplexity(SyntaxNode CodeBlock)

private static int GetCyclomaticComplexity(SyntaxNodeOrToken[] nodesAndTokens)
{
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 =>
return nodesAndTokens.Count(syntaxNodeOrToken => IsComplexKind(syntaxNodeOrToken.Kind)) + 1;
}

private static bool IsOperandKind(SyntaxKind kind)
{
switch (kind)
{
if (node.IsNode)
{
return ComplexKinds.Contains(
node.AsNode().Kind);
}
if (node.IsToken)
{
return ComplexKinds.Contains(
node.AsToken().Kind);
}
else return false;
});
return nodes + 1;
case SyntaxKind.IdentifierToken:
case SyntaxKind.Int32LiteralToken:
case SyntaxKind.StringLiteralToken:
case SyntaxKind.BooleanLiteralValue:
case SyntaxKind.TrueKeyword:
case SyntaxKind.FalseKeyword:
return true;
}

return false;
}

private static bool IsComplexKind(SyntaxKind kind)
{
switch (kind)
{
case SyntaxKind.IfKeyword:
case SyntaxKind.ElifKeyword:
case SyntaxKind.LogicalAndExpression:
case SyntaxKind.LogicalOrExpression:
case SyntaxKind.CaseLine:
case SyntaxKind.ForKeyword:
case SyntaxKind.ForEachKeyword:
case SyntaxKind.WhileKeyword:
case SyntaxKind.UntilKeyword:
return true;
}

return false;
}

}
}

Expand Down
2 changes: 1 addition & 1 deletion LinterCopAnalyzers.resx
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@
<value>Cyclomatic complexity and Maintainability index</value>
</data>
<data name="Rule0009CodeMetricsInfoFormat" xml:space="preserve">
<value>Cyclomatic complexity: {0}/({1}), Maintainability index: {2}/({3})</value>
<value>Cyclomatic complexity: {0} (threshold &gt;={1}), Maintainability index: {2} of 100 (threshold &lt;={3})</value>
</data>
<data name="Rule0009CodeMetricsInfoTitle" xml:space="preserve">
<value>Cyclomatic complexity and Maintainability index</value>
Expand Down

0 comments on commit 4391d44

Please sign in to comment.