From 1fa2adcf5b847f5e424d679b7d6f98c012f8e8f5 Mon Sep 17 00:00:00 2001 From: NeVeSpl Date: Thu, 7 Dec 2023 15:48:23 +0100 Subject: [PATCH] add rules: HaveNumberOfLinesOfCodeLowerThan / HaveNumberOfLinesOfCodeGreaterThan --- README.md | 2 +- sources/NetArchTest/Condition_Metrics.cs | 30 +++++ .../Mono.Cecil/MethodDefinitionExtensions.cs | 21 +++ .../TypeDefinitionExtensions.GetLOC.cs | 52 +++++++ .../Mono.Cecil/TypeDefinitionExtensions.cs | 2 +- .../Functions/FunctionDelegates_Metrics.cs | 24 ++++ sources/NetArchTest/Predicate_Metrics.cs | 30 +++++ .../ConditionTests_Metrics.cs | 44 ++++++ .../PredicateTests_Metrics.cs | 41 ++++++ .../Metrics/ClassLarge.cs | 127 ++++++++++++++++++ .../Metrics/ClassSmall.cs | 29 ++++ 11 files changed, 400 insertions(+), 2 deletions(-) create mode 100644 sources/NetArchTest/Condition_Metrics.cs create mode 100644 sources/NetArchTest/Extensions/Mono.Cecil/MethodDefinitionExtensions.cs create mode 100644 sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.GetLOC.cs create mode 100644 sources/NetArchTest/Functions/FunctionDelegates_Metrics.cs create mode 100644 sources/NetArchTest/Predicate_Metrics.cs create mode 100644 tests/NetArchTest.Rules.UnitTests/ConditionTests_Metrics.cs create mode 100644 tests/NetArchTest.Rules.UnitTests/PredicateTests_Metrics.cs create mode 100644 tests/NetArchTest.TestStructure/Metrics/ClassLarge.cs create mode 100644 tests/NetArchTest.TestStructure/Metrics/ClassSmall.cs diff --git a/README.md b/README.md index 9d5e3c6..c070d2b 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ What **eNhancedEdition** has to offer, that is not available in the NetArchTest - at the end, you get more information which should make reasoning about tests easier: -![revit-database-scripting-update-query](documentation/result.printscreen.png) +![revit-database-scripting-update-query](https://raw.githubusercontent.com/NeVeSpl/NetArchTest.eNhancedEdition/main/documentation/result.printscreen.png) diff --git a/sources/NetArchTest/Condition_Metrics.cs b/sources/NetArchTest/Condition_Metrics.cs new file mode 100644 index 0000000..8359718 --- /dev/null +++ b/sources/NetArchTest/Condition_Metrics.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NetArchTest.Functions; + +namespace NetArchTest.Rules +{ + public sealed partial class Condition + { + /// + /// Selects types that have more logical lines of code than a given number + /// + /// An updated set of conditions that can be applied to a list of types. + public ConditionList HaveNumberOfLinesOfCodeGreaterThan(int number) + { + AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNumberOfLinesOfCodeFewerThan(context, inputTypes, number, false)); + return CreateConditionList(); + } + + /// + /// Selects types that have fewer logical lines of code than a given number + /// + /// An updated set of conditions that can be applied to a list of types. + public ConditionList HaveNumberOfLinesOfCodeLowerThan(int number) + { + AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNumberOfLinesOfCodeFewerThan(context, inputTypes, number, true)); + return CreateConditionList(); + } + } +} diff --git a/sources/NetArchTest/Extensions/Mono.Cecil/MethodDefinitionExtensions.cs b/sources/NetArchTest/Extensions/Mono.Cecil/MethodDefinitionExtensions.cs new file mode 100644 index 0000000..066d928 --- /dev/null +++ b/sources/NetArchTest/Extensions/Mono.Cecil/MethodDefinitionExtensions.cs @@ -0,0 +1,21 @@ +using System.CodeDom.Compiler; +using System.Linq; +using System.Runtime.CompilerServices; +using Mono.Cecil; + +namespace NetArchTest.Extensions.Mono.Cecil +{ + internal static class MethodDefinitionExtensions + { + public static bool IsGeneratedCode(this MethodDefinition method) + { + if (method == null) + return false; + + if (method.HasCustomAttributes == false) + return false; + + return method.CustomAttributes.Any(x => x?.AttributeType?.FullName == typeof(CompilerGeneratedAttribute).FullName || x?.AttributeType?.FullName == typeof(GeneratedCodeAttribute).FullName); + } + } +} \ No newline at end of file diff --git a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.GetLOC.cs b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.GetLOC.cs new file mode 100644 index 0000000..48fd61c --- /dev/null +++ b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.GetLOC.cs @@ -0,0 +1,52 @@ +using Mono.Cecil; + +namespace NetArchTest.Extensions.Mono.Cecil +{ + static internal partial class TypeDefinitionExtensions + { + public static int GetNumberOfLogicalLinesOfCode(this TypeDefinition type) + { + int count = 0; + + if (type.HasMethods) + { + foreach (var method in type.Methods) + { + if (!method.HasBody) continue; + if (method.IsGeneratedCode()) continue; + if (method.DeclaringType.Module.HasSymbols == false) continue; + + var methodLLOC = CountLogicalLinesOfCode(method); + count += methodLLOC; + } + } + + return count; + } + + + private static int CountLogicalLinesOfCode(MethodDefinition method) + { + int count = 0; + int lastLine = int.MinValue; + + foreach (var instruction in method.Body.Instructions) + { + var sequencePoint = method.DebugInformation.GetSequencePoint(instruction); + if (sequencePoint == null) + continue; + + int line = sequencePoint.StartLine; + // special value for PDB (so that debuggers can ignore a line) + if (line == 0xFEEFEE) + continue; + + if (line > lastLine) + count++; + + lastLine = line; + } + return count; + } + } +} \ No newline at end of file diff --git a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs index 4537fc2..c11597d 100644 --- a/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs +++ b/sources/NetArchTest/Extensions/Mono.Cecil/TypeDefinitionExtensions.cs @@ -6,7 +6,7 @@ namespace Mono.Cecil { - static internal class TypeDefinitionExtensions + static internal partial class TypeDefinitionExtensions { public static bool IsSubclassOf(this TypeReference child, TypeReference parent) { diff --git a/sources/NetArchTest/Functions/FunctionDelegates_Metrics.cs b/sources/NetArchTest/Functions/FunctionDelegates_Metrics.cs new file mode 100644 index 0000000..0ea871d --- /dev/null +++ b/sources/NetArchTest/Functions/FunctionDelegates_Metrics.cs @@ -0,0 +1,24 @@ +using System.Collections.Generic; +using System.Linq; +using NetArchTest.Assemblies; +using NetArchTest.Extensions.Mono.Cecil; +using NetArchTest.RuleEngine; + +namespace NetArchTest.Functions +{ + internal static partial class FunctionDelegates + { + + internal static IEnumerable HaveNumberOfLinesOfCodeFewerThan(FunctionSequenceExecutionContext context, IEnumerable input, int number, bool condition) + { + if (condition) + { + return input.Where(c => c.Definition.GetNumberOfLogicalLinesOfCode() < number); + } + else + { + return input.Where(c => !(c.Definition.GetNumberOfLogicalLinesOfCode() < number)); + } + } + } +} \ No newline at end of file diff --git a/sources/NetArchTest/Predicate_Metrics.cs b/sources/NetArchTest/Predicate_Metrics.cs new file mode 100644 index 0000000..143d4ed --- /dev/null +++ b/sources/NetArchTest/Predicate_Metrics.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; +using NetArchTest.Functions; + +namespace NetArchTest.Rules +{ + public sealed partial class Predicate + { + /// + /// Selects types that have more logical lines of code than a given number + /// + /// An updated set of conditions that can be applied to a list of types. + public PredicateList HaveNumberOfLinesOfCodeGreaterThan(int number) + { + AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNumberOfLinesOfCodeFewerThan(context, inputTypes, number, false)); + return CreatePredicateList(); + } + + /// + /// Selects types that have fewer logical lines of code than a given number + /// + /// An updated set of conditions that can be applied to a list of types. + public PredicateList HaveNumberOfLinesOfCodeLowerThan(int number) + { + AddFunctionCall((context, inputTypes) => FunctionDelegates.HaveNumberOfLinesOfCodeFewerThan(context, inputTypes, number, true)); + return CreatePredicateList(); + } + } +} \ No newline at end of file diff --git a/tests/NetArchTest.Rules.UnitTests/ConditionTests_Metrics.cs b/tests/NetArchTest.Rules.UnitTests/ConditionTests_Metrics.cs new file mode 100644 index 0000000..42a522b --- /dev/null +++ b/tests/NetArchTest.Rules.UnitTests/ConditionTests_Metrics.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using NetArchTest.TestStructure.Metrics; +using NetArchTest.UnitTests.TestFixtures; +using Xunit; + +namespace NetArchTest.UnitTests +{ + public class ConditionTests_Metrics(AllTypesFixture fixture) : IClassFixture + { + + [Fact(DisplayName = "HaveNumberOfLinesOfCodeLowerThan")] + public void HaveNumberOfLinesOfCodeLowerThan() + { + var result = fixture.Types + .That() + .ResideInNamespace(typeof(ClassSmall).Namespace) + .And() + .AreOfType(typeof(ClassSmall)) + .Should() + .HaveNumberOfLinesOfCodeLowerThan(13).GetResult(); + + Assert.True(result.IsSuccessful); + } + + + [Fact(DisplayName = "HaveNumberOfLinesOfCodeGreaterThan")] + public void HaveNumberOfLinesOfCodeGreaterThan() + { + var result = fixture.Types + .That() + .ResideInNamespace(typeof(ClassSmall).Namespace) + .And() + .AreOfType(typeof(ClassLarge)) + .Should() + .HaveNumberOfLinesOfCodeGreaterThan(13).GetResult(); + + Assert.True(result.IsSuccessful); + } + } +} diff --git a/tests/NetArchTest.Rules.UnitTests/PredicateTests_Metrics.cs b/tests/NetArchTest.Rules.UnitTests/PredicateTests_Metrics.cs new file mode 100644 index 0000000..33caa8c --- /dev/null +++ b/tests/NetArchTest.Rules.UnitTests/PredicateTests_Metrics.cs @@ -0,0 +1,41 @@ +using System; +using NetArchTest.TestStructure.Metrics; +using NetArchTest.UnitTests.TestFixtures; +using Xunit; +using static NetArchTest.Utils; + +namespace NetArchTest.UnitTests +{ + public class PredicateTests_Metrics(AllTypesFixture fixture) : IClassFixture + { + + [Fact(DisplayName = "HaveNumberOfLinesOfCodeLowerThan")] + public void HaveNumberOfLinesOfCodeLowerThan() + { + var result = fixture.Types + .That() + .ResideInNamespace(namespaceof()) + .And() + .HaveNumberOfLinesOfCodeLowerThan(13) + .GetReflectionTypes(); + + Assert.Single(result); + Assert.Contains(typeof(ClassSmall), result); + } + + [Fact(DisplayName = "HaveNumberOfLinesOfCodeGreaterThan")] + public void HaveNumberOfLinesOfCodeGreaterThan() + { + var result = fixture.Types + .That() + .ResideInNamespace(namespaceof()) + .And() + .HaveNumberOfLinesOfCodeGreaterThan(13) + .GetReflectionTypes(); + + Assert.Single(result); + Assert.Contains(typeof(ClassLarge), result); + } + + } +} diff --git a/tests/NetArchTest.TestStructure/Metrics/ClassLarge.cs b/tests/NetArchTest.TestStructure/Metrics/ClassLarge.cs new file mode 100644 index 0000000..3ab5c1d --- /dev/null +++ b/tests/NetArchTest.TestStructure/Metrics/ClassLarge.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetArchTest.TestStructure.Metrics +{ + // This class was generated by GPT-4 Turbo :D + public class ClassLarge + { + // Fields + private string owner; + private double balance; + private double interestRate; + + // Constructors + public ClassLarge() + { + owner = "Unknown"; + balance = 0.0; + interestRate = 0.0; + } + + public ClassLarge(string owner, double balance, double interestRate) + { + this.owner = owner; + this.balance = balance; + this.interestRate = interestRate; + } + + // Methods + public void Deposit(double amount) + { + // Check if the amount is positive + if (amount > 0) + { + // Add the amount to the balance + balance += amount; + // Print a confirmation message + Console.WriteLine("You have deposited {0:C} to your account.", amount); + } + else + { + // Print an error message + Console.WriteLine("Invalid amount. Please enter a positive number."); + } + } + + public void Withdraw(double amount) + { + // Check if the amount is positive + if (amount > 0) + { + // Check if the amount is less than or equal to the balance + if (amount <= balance) + { + // Subtract the amount from the balance + balance -= amount; + // Print a confirmation message + Console.WriteLine("You have withdrawn {0:C} from your account.", amount); + } + else + { + // Print an error message + Console.WriteLine("Insufficient funds. You cannot withdraw more than your balance."); + } + } + else + { + // Print an error message + Console.WriteLine("Invalid amount. Please enter a positive number."); + } + } + + public void Transfer(double amount, ClassLarge other) + { + // Check if the other account is not null + if (other != null) + { + // Check if the amount is positive + if (amount > 0) + { + // Check if the amount is less than or equal to the balance + if (amount <= balance) + { + // Subtract the amount from the balance + balance -= amount; + // Add the amount to the other account's balance + other.balance += amount; + // Print a confirmation message + Console.WriteLine("You have transferred {0:C} to {1}'s account.", amount, other.owner); + } + else + { + // Print an error message + Console.WriteLine("Insufficient funds. You cannot transfer more than your balance."); + } + } + else + { + // Print an error message + Console.WriteLine("Invalid amount. Please enter a positive number."); + } + } + else + { + // Print an error message + Console.WriteLine("Invalid account. Please enter a valid account."); + } + } + + public void ApplyInterest() + { + // Calculate the interest amount + double interest = balance * interestRate / 100; + // Add the interest amount to the balance + balance += interest; + // Print a confirmation message + Console.WriteLine("You have earned {0:C} in interest.", interest); + } + + // Override ToString method + public override string ToString() + { + return $"Owner: {owner}, Balance: {balance:C}, Interest Rate: {interestRate}%"; + } + } +} diff --git a/tests/NetArchTest.TestStructure/Metrics/ClassSmall.cs b/tests/NetArchTest.TestStructure/Metrics/ClassSmall.cs new file mode 100644 index 0000000..ffe6b91 --- /dev/null +++ b/tests/NetArchTest.TestStructure/Metrics/ClassSmall.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace NetArchTest.TestStructure.Metrics +{ + internal class ClassSmall + { + // This method was generated by GPT-4 Turbo :D + public static string Foo(string s) + { + if (string.IsNullOrEmpty(s)) + { + return s; + } + + string[] words = s.Split(); + + StringBuilder result = new StringBuilder(); + + foreach (string word in words) + { + result.Append(char.ToUpper(word[0]) + word.Substring(1) + " "); + } + + return result.ToString().Trim(); + } + } +}