Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #10 from alexvictoor/master

Architecture rules engine & LCOM4 bug fixes
  • Loading branch information...
commit 0edc42acb33c0428b13c97df16b6d3ee691896b2 2 parents f25360d + 6ab5996
@grozeille authored
Showing with 1,187 additions and 315 deletions.
  1. +77 −0 DependencyParser.Test/ArchitectureRuleEngineTest.cs
  2. +20 −0 DependencyParser.Test/CecilHelper.cs
  3. +104 −0 DependencyParser.Test/DependencyAnalyzerTest.cs
  4. +15 −1 DependencyParser.Test/DependencyParser.Test.csproj
  5. +1 −3 DependencyParser.Test/DependencyParserTest.cs
  6. +97 −0 DependencyParser.Test/DesignMeasuresTest.cs
  7. +0 −71 DependencyParser.Test/DesignMeasuresWriterTest.cs
  8. +1 −9 DependencyParser.Test/DethOfInheritanceTreeAnalyzerTest.cs
  9. +185 −20 DependencyParser.Test/Lcom4AnalyzerTest.cs
  10. +3 −12 DependencyParser.Test/ResponseForClassAnalyzerTest.cs
  11. +42 −0 DependencyParser.Test/WildcardPatternMatcherTest.cs
  12. +1 −1  DependencyParser.Test/lcom4-expected.xml
  13. BIN  DependencyParser.Test/testdata/external/Spring.Core.dll
  14. +8 −15 DependencyParser.Test/testdata/test.xml
  15. +1 −0  DependencyParser.Test/types-merged-expected.xml
  16. +25 −0 DependencyParser/ArchitectureRule.cs
  17. +57 −0 DependencyParser/ArchitectureRuleEngine.cs
  18. +33 −0 DependencyParser/ArchitectureViolation.cs
  19. +135 −0 DependencyParser/DependencyAnalyzer.cs
  20. +8 −1 DependencyParser/DependencyParser.csproj
  21. +51 −15 DependencyParser/{DesignMeasuresWriter.cs → DesignMeasures.cs}
  22. +40 −3 DependencyParser/Lcom4Analyzer.cs
  23. +117 −163 DependencyParser/Program.cs
  24. +1 −1  DependencyParser/ResponseForClassAnalyzer.cs
  25. +57 −0 DependencyParser/SourceHelper.cs
  26. +58 −0 DependencyParser/SourceRegistry.cs
  27. +48 −0 DependencyParser/WildcardPatternMatcher.cs
  28. +2 −0  pom.xml
View
77 DependencyParser.Test/ArchitectureRuleEngineTest.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Mono.Cecil;
+using NUnit.Framework;
+
+namespace DependencyParser.Test {
+
+
+ [NUnit.Framework.TestFixture]
+ public class ArchitectureRuleEngineTest {
+
+ private ArchitectureRuleEngine engine;
+
+
+ [SetUp]
+ public void SetUp()
+ {
+ engine = new ArchitectureRuleEngine() {};
+ }
+
+ [Test]
+ public void Should_Find_Forbidden_Dependencies()
+ {
+ engine.Init("*:DependencyParser.*");
+ var dependencies = new TypeReference[] { typeof(Foo).GetCecilType() };
+ var result =
+ engine.FindArchitectureViolation(typeof(Bar).GetCecilType(), dependencies);
+ Assert.AreEqual(1, result.Count());
+ Assert.AreEqual("DependencyParser.Test.Foo", result.First().Dependency.FullName);
+ }
+
+ [Test]
+ public void Should_Not_Used_Unrelated_Rules_For_Source_Types()
+ {
+ engine.Init("Xyz.*:DependencyParser.*");
+ var dependencies = new TypeReference[] { typeof(Foo).GetCecilType() };
+ var result =
+ engine.FindArchitectureViolation(typeof(Bar).GetCecilType(), dependencies);
+ Assert.AreEqual(0, result.Count());
+ }
+
+ [Test]
+ public void Should_Not_Used_Unrelated_Rules_For_Destination_Types()
+ {
+ engine.Init("DependencyParser.*:xyz.*");
+ var dependencies = new TypeReference[] { typeof(Foo).GetCecilType() };
+ var result =
+ engine.FindArchitectureViolation(typeof(Bar).GetCecilType(), dependencies);
+ Assert.AreEqual(0, result.Count());
+ }
+
+ [Test]
+ public void Should_Not_Report_Twice_The_Same_Forbidden_Dependency()
+ {
+ engine.Init("*:DependencyParser.*,*:DependencyParser.Test.*");
+ var dependencies = new TypeReference[] { typeof(Foo).GetCecilType() };
+ var result =
+ engine.FindArchitectureViolation(typeof(Bar).GetCecilType(), dependencies);
+ Assert.AreEqual(1, result.Count());
+ Assert.AreEqual("DependencyParser.Test.Foo", result.First().Dependency.FullName);
+ }
+
+ }
+
+ public class Foo
+ {
+
+ }
+
+ public class Bar {
+
+ }
+
+}
View
20 DependencyParser.Test/CecilHelper.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Mono.Cecil;
+
+namespace DependencyParser.Test {
+
+ public static class CecilHelper {
+
+ public static TypeDefinition GetCecilType(this Type t)
+ {
+ string name = t.FullName;
+ string unit = Assembly.GetExecutingAssembly().Location;
+ var assembly = AssemblyDefinition.ReadAssembly(unit);
+ return assembly.MainModule.GetType(name);
+ }
+ }
+}
View
104 DependencyParser.Test/DependencyAnalyzerTest.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Mono.Cecil;
+using NUnit.Framework;
+
+namespace DependencyParser.Test {
+
+
+ [NUnit.Framework.TestFixture]
+ public class DependencyAnalyzerTest {
+
+ private DependencyAnalyzer analyzer;
+
+
+ [SetUp]
+ public void SetUp()
+ {
+ analyzer = new DependencyAnalyzer();
+ }
+
+ [Test]
+ public void Should_Find_Dependencies_From_Attributes()
+ {
+ var dependencies = analyzer.FindTypeDependencies(this.GetType().GetCecilType());
+ Assert.AreEqual(1, dependencies.Count(n => n.FullName == typeof(TestAttribute).FullName));
+ }
+
+ [Test]
+ public void Should_Find_Dependencies_From_Fields()
+ {
+ var dependencies = analyzer.FindTypeDependencies(this.GetType().GetCecilType());
+ Assert.AreEqual(1, dependencies.Count(n => n.FullName == typeof(DependencyAnalyzer).FullName));
+ }
+
+ [Test]
+ public void Should_Find_Dependencies_From_Properties()
+ {
+ var dependencies = analyzer.FindTypeDependencies(typeof(ClassWithProperty).GetCecilType());
+ Assert.IsTrue(dependencies.Contains(typeof(Thread).GetCecilType()));
+ }
+
+ [Test]
+ public void Should_Find_Dependencies_From_Parameters()
+ {
+ var dependencies = analyzer.FindTypeDependencies(typeof(ClassWithCallsOnParameters).GetCecilType());
+ Assert.IsTrue(dependencies.Contains(typeof(Thread).GetCecilType()));
+ }
+
+ [Test]
+ public void Should_Find_Dependencies_From_Array()
+ {
+ var dependencies = analyzer.FindTypeDependencies(typeof(ClassWithArray).GetCecilType());
+ Assert.IsTrue(dependencies.Contains(typeof(Thread).GetCecilType()));
+ }
+
+ [Test]
+ public void Should_Ignore_System_Dependencies()
+ {
+ var dependencies = analyzer.FindTypeDependencies(typeof(ClassWithSystemDependency).GetCecilType());
+ dependencies = analyzer.FilterSystemDependencies(dependencies);
+ Assert.AreEqual(0, dependencies.Count(n => n.FullName.Contains("Dictionary")));
+ }
+ }
+
+ public class ClassWithProperty {
+
+ public Thread MyThread { get; set; }
+ }
+
+ public abstract class ClassWithCallsOnParameters {
+
+ public void Stop(Thread thread)
+ {
+ }
+
+ public void Start(Thread thread)
+ {
+ }
+
+ }
+
+ public abstract class ClassWithArray {
+ public void Stop(Thread[] threads)
+ {
+ }
+ }
+
+ public abstract class ClassWithSystemDependency {
+
+ private IDictionary<string, string> dic = new Dictionary<string, string>();
+
+ public void Stop(Thread[] threads)
+ {
+ }
+ }
+
+ public class Thread
+ {
+
+ }
+}
View
16 DependencyParser.Test/DependencyParser.Test.csproj
@@ -37,6 +37,10 @@
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
+ <Reference Include="Gendarme.Framework, Version=2.11.0.0, Culture=neutral, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Mono.Gendarme.2.11.0.20121120\tools\Gendarme.Framework.dll</HintPath>
+ </Reference>
<Reference Include="Mono.Cecil">
<HintPath>..\packages\Mono.Cecil.0.9.5.4\lib\net40\Mono.Cecil.dll</HintPath>
</Reference>
@@ -52,12 +56,16 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="ArchitectureRuleEngineTest.cs" />
+ <Compile Include="CecilHelper.cs" />
+ <Compile Include="DependencyAnalyzerTest.cs" />
<Compile Include="DependencyParserTest.cs" />
<Compile Include="DethOfInheritanceTreeAnalyzerTest.cs" />
<Compile Include="Lcom4AnalyzerTest.cs" />
- <Compile Include="DesignMeasuresWriterTest.cs" />
+ <Compile Include="DesignMeasuresTest.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ResponseForClassAnalyzerTest.cs" />
+ <Compile Include="WildcardPatternMatcherTest.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="lcom4-expected.xml">
@@ -69,10 +77,16 @@
<Content Include="testdata\Example.Core.pdb">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
+ <Content Include="testdata\external\Spring.Core.dll">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
<Content Include="testdata\test.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<SubType>Designer</SubType>
</Content>
+ <Content Include="types-merged-expected.xml">
+ <CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ </Content>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
View
4 DependencyParser.Test/DependencyParserTest.cs
@@ -14,13 +14,11 @@ public class DependencyParserTest {
public void Should_Create_An_XML_Report()
{
File.Delete("test.xml");
- Program.Main(new string[] { "-a=testdata/Example.Core.dll", "-o=test.xml" });
+ Program.Main(new string[] { "-a=testdata/Example.Core.dll", "-o=test.xml", "-d=yes" });
string expected = File.ReadAllText("testdata/test.xml");
string result = File.ReadAllText("test.xml");
Assert.AreEqual(expected, result);
-
-
}
}
}
View
97 DependencyParser.Test/DesignMeasuresTest.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Xml;
+using Mono.Cecil;
+using NUnit.Framework;
+
+namespace DependencyParser.Test {
+
+ [NUnit.Framework.TestFixture]
+ public class DesignMeasuresTest {
+ [Test]
+ public void Should_Write_Correct_XML()
+ {
+ File.Delete("lcom4-generated.xml");
+ using (var stream = new FileStream("lcom4-generated.xml", FileMode.Create))
+ {
+ using (var writer = new XmlTextWriter(stream, Encoding.UTF8))
+ {
+ writer.Formatting = Formatting.Indented;
+ var measure = new DesignMeasures();
+ var typeDefinition = typeof(SimpleClassWithTwoFields).GetCecilType();
+ var blocks = new HashSet<HashSet<MemberReference>>();
+
+ foreach (var mth in typeDefinition.Methods)
+ {
+ var block = new HashSet<MemberReference> { mth, typeDefinition.Fields.First() };
+ blocks.Add(block);
+ }
+ measure.Type = typeDefinition;
+ measure.Lcom4Blocks = blocks;
+ measure.ResponseForClass = 42;
+ measure.DethOfInheritance = 17;
+
+ measure.Write(writer);
+ }
+ }
+ string expected = File.ReadAllText("lcom4-expected.xml");
+ string result = File.ReadAllText("lcom4-generated.xml");
+
+ Assert.AreEqual(expected, result);
+ }
+
+ [Test]
+ public void Should_Merge_Measures()
+ {
+ File.Delete("types-merged-generated.xml");
+ var measure = new DesignMeasures();
+ var typeDefinition = typeof(SimpleClassWithTwoFields).GetCecilType();
+ measure.Type = typeDefinition;
+ measure.ResponseForClass = 42;
+ measure.DethOfInheritance = 17;
+ measure.Lcom4Blocks = Enumerable.Empty<IEnumerable<MemberReference>>();
+
+ var measure2 = new DesignMeasures();
+ var typeDefinition2 = typeof(DesignMeasuresTest).GetCecilType();
+ measure2.Type = typeDefinition2;
+ measure2.ResponseForClass = 36;
+ measure2.DethOfInheritance = 42;
+ measure2.Lcom4Blocks = Enumerable.Empty<IEnumerable<MemberReference>>();
+
+ var mergedMeasures = measure.Merge(measure2);
+ using (var stream = new FileStream("types-merged-generated.xml", FileMode.Create))
+ {
+ using (var writer = new XmlTextWriter(stream, Encoding.UTF8))
+ {
+ writer.Formatting = Formatting.Indented;
+ mergedMeasures.Write(writer);
+ }
+ }
+
+ string expected = File.ReadAllText("types-merged-expected.xml");
+ string result = File.ReadAllText("types-merged-generated.xml");
+ Assert.AreEqual(expected, result);
+
+ }
+ }
+
+ public class SimpleClassWithTwoFields {
+ private int fieldA;
+
+ private int fieldB;
+
+ public void doA()
+ {
+ fieldA++;
+ }
+
+ public void doB()
+ {
+ fieldB++;
+ }
+ }
+}
View
71 DependencyParser.Test/DesignMeasuresWriterTest.cs
@@ -1,71 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using System.Xml;
-using Mono.Cecil;
-using NUnit.Framework;
-
-namespace DependencyParser.Test {
-
- [NUnit.Framework.TestFixture]
- public class DesignMeasuresWriterTest {
- [Test]
- public void Should_Generate()
- {
- File.Delete("lcom4-generated.xml");
- using (var stream = new FileStream("lcom4-generated.xml", FileMode.Create))
- {
- using (var writer = new XmlTextWriter(stream, Encoding.UTF8))
- {
- writer.Formatting = Formatting.Indented;
- var designWriter = new DesignMeasuresWriter();
- var typeDefinition = getType("DependencyParser.Test.SimpleClassWithTwoFields");
- var blocks = new HashSet<HashSet<MemberReference>>();
-
- foreach (var mth in typeDefinition.Methods)
- {
- var block = new HashSet<MemberReference> { mth, typeDefinition.Fields.First() };
- blocks.Add(block);
- }
- designWriter.Xml = writer;
- designWriter.Type = typeDefinition;
- designWriter.Lcom4Blocks = blocks;
- designWriter.ResponseForClass = 42;
- designWriter.DethOfInheritance = 17;
-
- designWriter.Write();
- }
- }
- string expected = File.ReadAllText("lcom4-expected.xml");
- string result = File.ReadAllText("lcom4-generated.xml");
-
- Assert.AreEqual(expected, result);
- }
-
- private TypeDefinition getType(string name)
- {
- string unit = Assembly.GetExecutingAssembly().Location;
- var assembly = AssemblyDefinition.ReadAssembly(unit);
- return assembly.MainModule.GetType(name);
- }
- }
-
- public class SimpleClassWithTwoFields {
- private int fieldA;
-
- private int fieldB;
-
- public void doA()
- {
- fieldA++;
- }
-
- public void doB()
- {
- fieldB++;
- }
- }
-}
View
10 DependencyParser.Test/DethOfInheritanceTreeAnalyzerTest.cs
@@ -14,17 +14,9 @@ public class DethOfInheritanceTreeAnalyzerTest {
[Test]
public void Should_Compute_DIT()
{
- var t = GetType("DependencyParser.Test.DethOfInheritanceTreeAnalyzerTest");
+ var t = typeof(DethOfInheritanceTreeAnalyzerTest).GetCecilType();
var result = new DethOfInheritanceTreeAnalyzer().ComputeDIT(t);
Assert.AreEqual(1, result);
}
-
-
- private TypeDefinition GetType(string name)
- {
- string unit = Assembly.GetExecutingAssembly().Location;
- var assembly = AssemblyDefinition.ReadAssembly(unit);
- return assembly.MainModule.GetType(name);
- }
}
}
View
205 DependencyParser.Test/Lcom4AnalyzerTest.cs
@@ -1,8 +1,11 @@
using System;
+using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
+using System.Threading;
+using Gendarme.Framework;
using Mono.Cecil;
using NUnit.Framework;
@@ -15,15 +18,23 @@ public class Lcom4AnalyzerTest {
public void Should_Find_One_Block_On_A_Simple_Class()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.SimpleClass"));
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClass).GetCecilType());
Assert.AreEqual(1, blocks.Count);
}
[Test]
+ public void Should_Find_Two_Blocks_On_A_Simple_Stateless_Class()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleCalculator).GetCecilType());
+ Assert.AreEqual(2, blocks.Count);
+ }
+
+ [Test]
public void Should_Find_One_Block_On_A_Class_With_Strong_Cohesion()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.ClassWithManyMethodsAndStrongCohesion"));
+ var blocks = analyzer.FindLcomBlocks(typeof(ClassWithManyMethodsAndStrongCohesion).GetCecilType());
Assert.AreEqual(1, blocks.Count);
Assert.AreEqual(5, blocks.ElementAt(0).Count);
}
@@ -32,7 +43,7 @@ public void Should_Find_One_Block_On_A_Class_With_Strong_Cohesion()
public void Should_Ignore_Simple_Accessors()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.ClassWithProperties"));
+ var blocks = analyzer.FindLcomBlocks(typeof(ClassWithProperties).GetCecilType());
Assert.AreEqual(0, blocks.Count);
}
@@ -40,7 +51,7 @@ public void Should_Ignore_Simple_Accessors()
public void Should_Take_In_Account_Calls_To_Simple_Accessors()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.ClassWithCallsToAccessors"));
+ var blocks = analyzer.FindLcomBlocks(typeof(ClassWithCallsToAccessors).GetCecilType());
Assert.AreEqual(1, blocks.Count);
}
@@ -48,7 +59,7 @@ public void Should_Take_In_Account_Calls_To_Simple_Accessors()
public void Should_Take_In_Account_Complex_Accessors()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.ClassWithComplexProperties"));
+ var blocks = analyzer.FindLcomBlocks(typeof(ClassWithComplexProperties).GetCecilType());
Assert.AreEqual(2, blocks.Count);
}
@@ -56,7 +67,7 @@ public void Should_Take_In_Account_Complex_Accessors()
public void Should_Ignore_Abstract_Methods()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.AbstractClass"));
+ var blocks = analyzer.FindLcomBlocks(typeof(AbstractClass).GetCecilType());
Assert.AreEqual(1, blocks.Count);
}
@@ -64,7 +75,23 @@ public void Should_Ignore_Abstract_Methods()
public void Should_Take_In_Account_Calls_To_Abstract_Methods()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.AbstractTemplateClass"));
+ var blocks = analyzer.FindLcomBlocks(typeof(AbstractTemplateClass).GetCecilType());
+ Assert.AreEqual(1, blocks.Count);
+ }
+
+ [Test]
+ public void Should_Ignore_Empty_Methods()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(typeof(ClassWithEmptyMethods).GetCecilType());
+ Assert.AreEqual(0, blocks.Count);
+ }
+
+ [Test]
+ public void Should_Take_In_Account_Calls_Ignore_Empty_Methods()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(typeof(ClassWithCallsToEmptyMethods).GetCecilType());
Assert.AreEqual(1, blocks.Count);
}
@@ -72,7 +99,7 @@ public void Should_Take_In_Account_Calls_To_Abstract_Methods()
public void Should_Ignore_Constructors()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.SimpleClassWithCtr"));
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClassWithCtr).GetCecilType());
Assert.AreEqual(2, blocks.Count);
}
@@ -80,7 +107,7 @@ public void Should_Ignore_Constructors()
public void Should_Not_Fail_On_Empty_Interface()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.IEmpty"));
+ var blocks = analyzer.FindLcomBlocks(typeof(IEmpty).GetCecilType());
Assert.AreEqual(0, blocks.Count);
}
@@ -88,7 +115,7 @@ public void Should_Not_Fail_On_Empty_Interface()
public void Should_Not_Fail_On_Empty_Class()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.EmptyClass"));
+ var blocks = analyzer.FindLcomBlocks(typeof(EmptyClass).GetCecilType());
Assert.AreEqual(0, blocks.Count);
}
@@ -96,7 +123,7 @@ public void Should_Not_Fail_On_Empty_Class()
public void Should_Not_Take_In_Account_Dispose_Methods()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.SimpleDisposableClass"));
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleDisposableClass).GetCecilType());
Assert.AreEqual(1, blocks.Count);
}
@@ -104,7 +131,7 @@ public void Should_Not_Take_In_Account_Dispose_Methods()
public void Should_Not_Take_In_Account_Equals_Methods()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.SimpleClassWithEquals"));
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClassWithEquals).GetCecilType());
Assert.AreEqual(2, blocks.Count);
}
@@ -112,7 +139,7 @@ public void Should_Not_Take_In_Account_Equals_Methods()
public void Should_Not_Take_In_Account_ToString_Methods()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.SimpleClassWithToString"));
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClassWithToString).GetCecilType());
Assert.AreEqual(2, blocks.Count);
}
@@ -120,7 +147,7 @@ public void Should_Not_Take_In_Account_ToString_Methods()
public void Should_Not_Take_In_Account_Methods_From_Wrapped_Objects()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.SimpleClassWithDelegation"));
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClassWithDelegation).GetCecilType());
Assert.AreEqual(1, blocks.Count);
Assert.AreEqual(3, blocks.ElementAt(0).Count);
}
@@ -129,7 +156,7 @@ public void Should_Not_Take_In_Account_Methods_From_Wrapped_Objects()
public void Should_Not_Take_In_Account_Inherited_Methods()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.DerivedClass"));
+ var blocks = analyzer.FindLcomBlocks(typeof(DerivedClass).GetCecilType());
Assert.AreEqual(1, blocks.Count);
Assert.AreEqual(3, blocks.ElementAt(0).Count);
}
@@ -138,17 +165,68 @@ public void Should_Not_Take_In_Account_Inherited_Methods()
public void Should_Not_Take_In_Account_Static_Methods()
{
var analyzer = new Lcom4Analyzer();
- var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.SimpleClassWithStaticMethod"));
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClassWithStaticMethod).GetCecilType());
+ Assert.AreEqual(1, blocks.Count);
+ }
+
+ [Test]
+ public void Should_Not_Take_In_Account_Linq_Fields_In_Accessors()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(typeof(PropertyWithLinq).GetCecilType());
+ Assert.AreEqual(1, blocks.Count);
+ // get, set and backing field
+ Assert.AreEqual(3, blocks.First().Count);
+ }
+
+ [Test]
+ public void Should_Not_Take_In_Account_References_To_Static_Fields()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClassWithStaticField).GetCecilType());
+ Assert.AreEqual(1, blocks.Count);
+ Assert.AreEqual(1, blocks.First().Count);
+ }
+
+ [Test]
+ public void Should_Not_Take_In_Account_References_To_External_Fields()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClassWithReferenceToExternalField).GetCecilType());
Assert.AreEqual(1, blocks.Count);
+ Assert.AreEqual(1, blocks.First().Count);
+ }
+
+ [Test]
+ public void Should_Take_In_Account_Field_References()
+ {
+ var definition = AssemblyDefinition.ReadAssembly(@"testdata\external\Spring.Core.dll", new ReaderParameters { AssemblyResolver = AssemblyResolver.Resolver });
+
+ var clientType = definition.MainModule.GetType("Spring.Collections.Generic", "ReadOnlyDictionary`2");
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(clientType);
+ Assert.AreEqual(12, blocks.Count);
}
- private TypeDefinition GetType(string name)
+ [Test]
+ public void Should_Ignore_Empty_Virtual_Methods()
{
- string unit = Assembly.GetExecutingAssembly().Location;
- var assembly = AssemblyDefinition.ReadAssembly(unit);
- return assembly.MainModule.GetType(name);
+ var definition = AssemblyDefinition.ReadAssembly(@"testdata\external\Spring.Core.dll", new ReaderParameters { AssemblyResolver = AssemblyResolver.Resolver });
+
+ var clientType = definition.MainModule.GetType("Spring.Expressions.Parser.antlr.debug", "ParserListenerBase");
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(clientType);
+ Assert.AreEqual(0, blocks.Count);
}
+ [Test]
+ public void Should_Take_In_Account_The_Ignorable_Field_Names()
+ {
+ var analyzer = new Lcom4Analyzer() { IgnorableFieldNames = new string[] {"Counter"} } ;
+
+ var blocks = analyzer.FindLcomBlocks(typeof(SimpleClass).GetCecilType());
+ Assert.AreEqual(2, blocks.Count);
+ }
}
public class EmptyClass
@@ -175,6 +253,44 @@ public void Decrement()
}
}
+ public class SimpleClassWithStaticField {
+
+ private static string STRING = "FOO BAR";
+
+ public void Do(string param)
+ {
+ if (param == STRING) {
+ Console.WriteLine("whatever!");
+ }
+ }
+ }
+
+ public class SimpleClassHolder {
+ public string toto = "";
+ }
+
+ public class SimpleClassWithReferenceToExternalField {
+
+ public void Do(string param)
+ {
+ Console.WriteLine("whatever!" + new SimpleClassHolder().toto);
+
+ }
+ }
+
+ public class SimpleCalculator {
+
+ public int Add(int x, int y)
+ {
+ return x + y;
+ }
+
+ public int Sub(int x, int y)
+ {
+ return x - y;
+ }
+ }
+
public class SimpleClassWithCtr {
private int fieldA;
@@ -312,6 +428,38 @@ public void DoB()
}
+ public class ClassWithEmptyMethods {
+
+ public void Foo()
+ {
+ }
+
+ public void Bar()
+ {
+ }
+
+ public virtual void VirBar()
+ {
+ }
+ }
+
+ public class ClassWithCallsToEmptyMethods {
+
+ public void Foo()
+ {
+ VirBar();
+ }
+
+ public void Bar()
+ {
+ VirBar();
+ }
+
+ public virtual void VirBar()
+ {
+ }
+ }
+
public class SimpleDisposableClass : IDisposable
{
public void DoSomething()
@@ -441,4 +589,21 @@ public static SimpleClassWithStaticMethod create()
return new SimpleClassWithStaticMethod();
}
}
+
+ public class PropertyWithLinq {
+
+ private IEnumerable<string> ignorableFieldNames = new string[] { };
+
+ public IEnumerable<string> IgnorableFieldNames
+ {
+ get
+ {
+ return ignorableFieldNames;
+ }
+ set
+ {
+ ignorableFieldNames = from n in value select n.ToLowerInvariant();
+ }
+ }
+ }
}
View
15 DependencyParser.Test/ResponseForClassAnalyzerTest.cs
@@ -20,7 +20,7 @@ public void Should_Compute_RFC()
{
var analyzer = new ResponseForClassAnalyzer();
- Assert.AreEqual(6, analyzer.ComputeRFC(GetType("DependencyParser.Test.ClassA")));
+ Assert.AreEqual(6, analyzer.ComputeRFC(typeof(ClassA).GetCecilType()));
}
[Test]
@@ -28,7 +28,7 @@ public void Should_Ignore_Property_Accessors()
{
var analyzer = new ResponseForClassAnalyzer();
- Assert.AreEqual(1, analyzer.ComputeRFC(GetType("DependencyParser.Test.Country")));
+ Assert.AreEqual(1, analyzer.ComputeRFC(typeof(Country).GetCecilType()));
}
[Test]
@@ -36,17 +36,8 @@ public void Should_Take_In_Account_Computed_Properties()
{
var analyzer = new ResponseForClassAnalyzer();
- Assert.AreEqual(3, analyzer.ComputeRFC(GetType("DependencyParser.Test.Employee")));
+ Assert.AreEqual(3, analyzer.ComputeRFC(typeof(Employee).GetCecilType()));
}
-
- private TypeDefinition GetType(string name)
- {
- string unit = Assembly.GetExecutingAssembly().Location;
- var assembly = AssemblyDefinition.ReadAssembly(unit);
- return assembly.MainModule.GetType(name);
- }
-
-
}
public class ClassA {
View
42 DependencyParser.Test/WildcardPatternMatcherTest.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using NUnit.Framework;
+
+namespace DependencyParser.Test {
+
+ [NUnit.Framework.TestFixture]
+ public class WildcardPatternMatcherTest {
+
+ [Test]
+ public void Should_Evaluate_Patterns()
+ {
+ // Positive Tests
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("*", ""));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("?", " "));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("*", "a"));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("*", "ab"));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("?", "a"));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("*?", "abc"));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("?*", "abc"));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("*abc", "abc"));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("*abc*", "abc"));
+ Assert.IsTrue(WildcardPatternMatcher.MatchWildcardString("*a*bc*", "aXXXbc"));
+
+ // Negative Tests
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("*a", ""));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("a*", ""));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("?", ""));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("*b*", "a"));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("b*a", "ab"));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("??", "a"));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("*?", ""));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("??*", "a"));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("*abc", "abX"));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("*abc*", "Xbc"));
+ Assert.IsFalse(WildcardPatternMatcher.MatchWildcardString("*a*bc*", "ac"));
+ }
+
+ }
+}
View
2  DependencyParser.Test/lcom4-expected.xml
@@ -1,4 +1,4 @@
-<type fullName="DependencyParser.Test.SimpleClassWithTwoFields" rfc="42" dit="17">
+<type fullName="DependencyParser.Test.SimpleClassWithTwoFields" source="" rfc="42" dit="17">
<block>
<element type="Field" name="fieldA" />
<element type="Method" name="System.Void doA()" />
View
BIN  DependencyParser.Test/testdata/external/Spring.Core.dll
Binary file not shown
View
23 DependencyParser.Test/testdata/test.xml
@@ -9,26 +9,17 @@
<To fullname="Example.Core.Money" assemblyname="Example.Core" assemblyversion="1.0.0.0" />
<To fullname="Example.Core.MoneyBag" assemblyname="Example.Core" assemblyversion="1.0.0.0" />
</From>
- <From fullname="Example.Core.Model.SubType/Alex3" />
- <From fullname="Example.Core.Model.SubType" />
- <From fullname="Example.Core.Money/Alex2" />
- <From fullname="Example.Core.Money">
+ <From fullname="Example.Core.Money" path="c:\work\sonar-cs\dotnet\tools\dotnet-tools-commons\src\test\resources\solution\Example\Example.Core\Money.cs">
<To fullname="Example.Core.IMoney" assemblyname="Example.Core" assemblyversion="1.0.0.0" />
<To fullname="Example.Core.MoneyBag" assemblyname="Example.Core" assemblyversion="1.0.0.0" />
</From>
- <From fullname="Example.Core.Alex" />
- <From fullname="Example.Core.MoneyBag">
+ <From fullname="Example.Core.MoneyBag" path="c:\work\sonar-cs\dotnet\tools\dotnet-tools-commons\src\test\resources\solution\Example\Example.Core\MoneyBag.cs">
<To fullname="Example.Core.IMoney" assemblyname="Example.Core" assemblyversion="1.0.0.0" />
<To fullname="Example.Core.Money" assemblyname="Example.Core" assemblyversion="1.0.0.0" />
</From>
- <From fullname="Example.Core.SampleMeasure/Possible" />
- <From fullname="Example.Core.SampleMeasure" />
</TypeReferences>
<Design>
- <type fullName="&lt;Module&gt;" rfc="0" dit="0" />
- <type fullName="Example.Core.IMoney" rfc="0" dit="0" />
- <type fullName="Example.Core.Model.SubType" rfc="1" dit="1" />
- <type fullName="Example.Core.Money" rfc="24" dit="1">
+ <type fullName="Example.Core.Money" mergedTypes="Example.Core.Money,Example.Core.Alex" source="c:\work\sonar-cs\dotnet\tools\dotnet-tools-commons\src\test\resources\solution\Example\Example.Core\Money.cs" rfc="24" dit="1">
<block>
<element type="Field" name="fAmount" />
<element type="Field" name="fCurrency" />
@@ -47,8 +38,7 @@
<element type="Method" name="Example.Core.IMoney Subtract(Example.Core.IMoney)" />
</block>
</type>
- <type fullName="Example.Core.Alex" rfc="3" dit="1" />
- <type fullName="Example.Core.MoneyBag" rfc="44" dit="1">
+ <type fullName="Example.Core.MoneyBag" source="c:\work\sonar-cs\dotnet\tools\dotnet-tools-commons\src\test\resources\solution\Example\Example.Core\MoneyBag.cs" rfc="44" dit="1">
<block>
<element type="Field" name="fMonies" />
<element type="Method" name="Example.Core.IMoney AddMoney(Example.Core.Money)" />
@@ -67,7 +57,7 @@
<element type="Method" name="Example.Core.IMoney Subtract(Example.Core.IMoney)" />
</block>
</type>
- <type fullName="Example.Core.SampleMeasure" rfc="14" dit="1">
+ <type fullName="Example.Core.SampleMeasure" source="c:\work\sonar-cs\dotnet\tools\dotnet-tools-commons\src\test\resources\solution\Example\Example.Core\SampleMeasure.cs" rfc="14" dit="1">
<block>
<element type="Field" name="Size (property)" />
<element type="Field" name="actual" />
@@ -78,6 +68,9 @@
<element type="Method" name="System.String Compute(System.Int32)" />
</block>
</type>
+ <type fullName="&lt;Module&gt;" source="" rfc="0" dit="0" />
+ <type fullName="Example.Core.IMoney" source="" rfc="0" dit="0" />
+ <type fullName="Example.Core.Model.SubType" source="" rfc="1" dit="1" />
</Design>
</Assembly>
</Dependencies>
View
1  DependencyParser.Test/types-merged-expected.xml
@@ -0,0 +1 @@
+<type fullName="DependencyParser.Test.SimpleClassWithTwoFields" mergedTypes="DependencyParser.Test.SimpleClassWithTwoFields,DependencyParser.Test.DesignMeasuresTest" source="" rfc="42" dit="42" />
View
25 DependencyParser/ArchitectureRule.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Mono.Cecil;
+
+namespace DependencyParser {
+
+ public class ArchitectureRule {
+
+ public string FromPattern { get; set; }
+
+ public string ToPattern { get; set; }
+
+ public bool IsFromTypeApplies(TypeReference type)
+ {
+ return WildcardPatternMatcher.MatchWildcardString(FromPattern, type.FullName);
+ }
+
+ public bool IsToTypeApplies(TypeReference type)
+ {
+ return WildcardPatternMatcher.MatchWildcardString(ToPattern, type.FullName);
+ }
+ }
+}
View
57 DependencyParser/ArchitectureRuleEngine.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Mono.Cecil;
+
+namespace DependencyParser {
+
+
+ public class ArchitectureRuleEngine {
+
+ private IEnumerable<ArchitectureRule> rules = Enumerable.Empty<ArchitectureRule>();
+
+ public void Init(string s)
+ {
+ if (s == null) {
+ return;
+ }
+ var fromToPairs = s.Split(',');
+
+ rules =
+ from pair in fromToPairs
+ select new ArchitectureRule() {
+ FromPattern = pair.Split(':')[0],
+ ToPattern = pair.Split(':')[1]
+ };
+ }
+
+ public IEnumerable<ArchitectureViolation> FindArchitectureViolation(TypeDefinition fromType, IEnumerable<TypeReference> toTypes)
+ {
+ var applicableRules =
+ from r in rules
+ where r.IsFromTypeApplies(fromType)
+ select r;
+
+ var badDependencies =
+ from d in toTypes
+ from r in applicableRules
+ where r.IsToTypeApplies(d)
+ select new ArchitectureViolation() {Subject = fromType, Dependency = d, Rule = r};
+
+ return badDependencies.Distinct(new ViolationEqualityComparer());
+ }
+ }
+
+ public class ViolationEqualityComparer : IEqualityComparer<ArchitectureViolation> {
+ public bool Equals(ArchitectureViolation v1, ArchitectureViolation v2)
+ {
+ return v1.Dependency.FullName == v2.Dependency.FullName;
+ }
+
+ public int GetHashCode(ArchitectureViolation v)
+ {
+ return v.Dependency.FullName.GetHashCode();
+ }
+ }
+}
View
33 DependencyParser/ArchitectureViolation.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using Mono.Cecil;
+
+namespace DependencyParser {
+
+ public class ArchitectureViolation {
+
+ public ArchitectureRule Rule { get; set; }
+
+ public TypeDefinition Subject { get; set; }
+
+ public TypeReference Dependency { get; set;}
+
+ public void Write(XmlTextWriter xml)
+ {
+ xml.WriteStartElement("Violation");
+ xml.WriteAttributeString("fullname", Subject.FullName);
+ var path = Subject.GetSourcePath();
+ if (path != null) {
+ xml.WriteAttributeString("path", path);
+ }
+ xml.WriteAttributeString("fromPattern", Rule.FromPattern);
+ xml.WriteAttributeString("toPattern", Rule.ToPattern);
+ xml.WriteAttributeString("dependency", Dependency.FullName);
+ xml.WriteEndElement();
+
+ }
+ }
+}
View
135 DependencyParser/DependencyAnalyzer.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Mono.Cecil;
+
+namespace DependencyParser {
+
+
+ /// <summary>
+ /// Type analyzer that provides dependencies to other classes
+ /// </summary>
+ public class DependencyAnalyzer {
+
+ public IEnumerable<TypeReference> FindTypeDependencies(TypeDefinition t)
+ {
+ var result = new HashSet<TypeReference>(new TypeEqualityComparer());
+ FindTypeDependencies(result, t);
+ result.Remove(t);
+ return result;
+ }
+
+ /// <summary>
+ /// Remove dependencies to .Net framework
+ /// </summary>
+ /// <param name="unfilteredDependencies"></param>
+ /// <returns></returns>
+ public IEnumerable<TypeReference> FilterSystemDependencies(IEnumerable<TypeReference> unfilteredDependencies)
+ {
+ return from t in unfilteredDependencies
+ where
+ !(t.Scope.Name.Equals("mscorlib") || t.Scope.Name.StartsWith("System") || t.Scope.Name.StartsWith("Microsoft"))
+ select t;
+ }
+
+ public void FindTypeDependencies(ISet<TypeReference> result, TypeDefinition t)
+ {
+ // ignore generated types
+ if ((t.DeclaringType == null && t.Namespace.Equals(string.Empty)) || t.Name.StartsWith("<>")) {
+ return;
+ }
+
+ foreach (var n in t.NestedTypes) {
+ FindTypeDependencies(result, n);
+ }
+
+
+ foreach (var c in t.CustomAttributes) {
+ AddDependency(result, c.AttributeType);
+ }
+
+ if (t.BaseType != null) {
+ AddDependency(result, t.BaseType);
+ }
+
+ foreach (var i in t.Interfaces) {
+ AddDependency(result, i);
+ }
+
+ foreach (var e in t.Events) {
+ AddDependency(result, e.EventType);
+ }
+
+ foreach (var f in t.Fields) {
+ AddDependency(result, f.FieldType);
+ }
+
+ foreach (var p in t.Properties) {
+ AddDependency(result, p.PropertyType);
+ }
+
+ foreach (var m in t.Methods) {
+ AddDependency(result, m.ReturnType);
+
+ foreach (var p in m.Parameters) {
+ AddDependency(result, p.ParameterType);
+ }
+
+ foreach (var c in m.CustomAttributes) {
+ AddDependency(result, c.AttributeType);
+ }
+
+ if (m.Body != null) {
+
+ foreach (var v in m.Body.Variables) {
+ AddDependency(result, v.VariableType);
+ }
+
+ foreach (var e in m.Body.ExceptionHandlers) {
+ if (e.CatchType != null) {
+ AddDependency(result, e.CatchType);
+ }
+ }
+ }
+ }
+ }
+
+ private void AddDependency(ISet<TypeReference> result, TypeReference to)
+ {
+ // ignore generic parameters
+ if (to.IsGenericParameter || to.Namespace.Equals(string.Empty)) {
+ return;
+ }
+
+ if (to.IsArray) {
+ to = to.GetElementType();
+ }
+
+ if (to.IsGenericInstance) {
+ var generic = (GenericInstanceType)to;
+ foreach (var a in generic.GenericArguments) {
+ result.Add(a);
+ }
+ to = to.GetElementType();
+ }
+
+ result.Add(to);
+ }
+
+ }
+
+ public class TypeEqualityComparer : IEqualityComparer<TypeReference>
+ {
+ public bool Equals(TypeReference t1, TypeReference t2)
+ {
+ return t1.FullName == t2.FullName;
+ }
+
+ public int GetHashCode(TypeReference t)
+ {
+ return t.FullName.GetHashCode();
+ }
+ }
+}
View
9 DependencyParser/DependencyParser.csproj
@@ -78,12 +78,19 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="ArchitectureRule.cs" />
+ <Compile Include="ArchitectureRuleEngine.cs" />
+ <Compile Include="ArchitectureViolation.cs" />
+ <Compile Include="DependencyAnalyzer.cs" />
<Compile Include="DethOfInheritanceTreeAnalyzer.cs" />
<Compile Include="Lcom4Analyzer.cs" />
- <Compile Include="DesignMeasuresWriter.cs" />
+ <Compile Include="DesignMeasures.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ResponseForClassAnalyzer.cs" />
+ <Compile Include="SourceHelper.cs" />
+ <Compile Include="SourceRegistry.cs" />
+ <Compile Include="WildcardPatternMatcher.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
View
66 DependencyParser/DesignMeasuresWriter.cs → DependencyParser/DesignMeasures.cs
@@ -11,12 +11,10 @@ namespace DependencyParser {
/// <summary>
/// TODO: Update summary.
/// </summary>
- public class DesignMeasuresWriter {
+ public class DesignMeasures {
private static readonly ReferenceComparer comparer = new ReferenceComparer();
- public XmlTextWriter Xml { set; private get; }
-
public TypeDefinition Type { set; private get; }
public int ResponseForClass { set; private get; }
@@ -25,20 +23,58 @@ public class DesignMeasuresWriter {
public IEnumerable<IEnumerable<MemberReference>> Lcom4Blocks { set; private get; }
- public void Write()
+
+ private IEnumerable<string> mergedTypes;
+ private IEnumerable<string> MergedTypesNames
+ {
+ set
+ {
+ mergedTypes = value;
+ }
+
+ get
+ {
+ if (mergedTypes == null)
+ {
+ return new string[] { Type.FullName };
+ }
+ return mergedTypes;
+ }
+ }
+
+
+ public DesignMeasures Merge(DesignMeasures measures)
{
- Xml.WriteStartElement("type");
- Xml.WriteAttributeString("fullName", Type.FullName);
- Xml.WriteAttributeString("rfc", ResponseForClass.ToString());
- Xml.WriteAttributeString("dit", DethOfInheritance.ToString());
+ return new DesignMeasures() {
+ Type = Type,
+ MergedTypesNames = MergedTypesNames.Union(measures.MergedTypesNames),
+ ResponseForClass = Math.Max(ResponseForClass, measures.ResponseForClass),
+ DethOfInheritance = Math.Max(DethOfInheritance, measures.DethOfInheritance),
+ Lcom4Blocks = Lcom4Blocks.Union(measures.Lcom4Blocks)
+ };
+ }
+
+ public void Write(XmlTextWriter xml)
+ {
+ xml.WriteStartElement("type");
+ xml.WriteAttributeString("fullName", Type.FullName);
+
+ if (MergedTypesNames.Count()>1)
+ {
+ xml.WriteAttributeString("mergedTypes", string.Join(",", MergedTypesNames));
+ }
+
+ xml.WriteAttributeString("source", Type.GetSourcePath());
+ xml.WriteAttributeString("rfc", ResponseForClass.ToString());
+ xml.WriteAttributeString("dit", DethOfInheritance.ToString());
foreach (var block in Lcom4Blocks)
{
var orderedBlock = block.OrderBy(x => x, comparer);
- Xml.WriteStartElement("block");
+ xml.WriteStartElement("block");
foreach (var memberReference in orderedBlock)
{
- Xml.WriteStartElement("element");
+ xml.WriteStartElement("element");
var method = memberReference as MethodDefinition;
string name;
string elementType;
@@ -51,13 +87,13 @@ public void Write()
elementType = "Method";
name = BuildSignature(method);
}
- Xml.WriteAttributeString("type", elementType);
- Xml.WriteAttributeString("name", name);
- Xml.WriteEndElement();
+ xml.WriteAttributeString("type", elementType);
+ xml.WriteAttributeString("name", name);
+ xml.WriteEndElement();
}
- Xml.WriteEndElement();
+ xml.WriteEndElement();
}
- Xml.WriteEndElement();
+ xml.WriteEndElement();
}
private string BuildFieldName(FieldDefinition field)
View
43 DependencyParser/Lcom4Analyzer.cs
@@ -16,12 +16,26 @@ namespace DependencyParser {
/// </summary>
public class Lcom4Analyzer {
+ private IEnumerable<string> ignorableFieldNames = new string[] {};
+
+ public IEnumerable<string> IgnorableFieldNames
+ {
+ get
+ {
+ return ignorableFieldNames;
+ }
+ set
+ {
+ ignorableFieldNames = from n in value select n.ToLowerInvariant();
+ }
+ }
+
public HashSet<HashSet<MemberReference>> FindLcomBlocks(TypeDefinition t)
{
var memberBlocks = new Dictionary<MemberReference, HashSet<MemberReference>>();
foreach (var method in t.Methods)
{
- if (NeedToBeFiltered(t, method))
+ if (NeedToBeFiltered(t, method) || IsEmpty(method))
{
continue;
}
@@ -46,8 +60,17 @@ public HashSet<HashSet<MemberReference>> FindLcomBlocks(TypeDefinition t)
switch (inst.OpCode.OperandType)
{
case OperandType.InlineField:
- FieldDefinition fd = inst.Operand as FieldDefinition;
- if (null != fd && (!fd.IsGeneratedCode() || method.IsSetter || method.IsGetter))
+
+ FieldReference fr = inst.Operand as FieldReference;
+ if (fr==null || IgnorableFieldNames.Contains(fr.Name.ToLowerInvariant())) {
+ break;
+ }
+ FieldDefinition fd = fr as FieldDefinition;
+ if (fd == null)
+ {
+ fd = ((FieldReference) inst.Operand).Resolve();
+ }
+ if (null != fd && !NeedToBeFiltered(method, fd))
{
mr = fd;
}
@@ -97,6 +120,15 @@ private bool NeedToBeFiltered(TypeDefinition t, MethodDefinition method)
}
+ private bool NeedToBeFiltered(MethodDefinition method, FieldDefinition field)
+ {
+ return field.DeclaringType != method.DeclaringType
+ || field.IsStatic
+ || (field.IsGeneratedCode() && !(field.Name.Contains("BackingField") && (method.IsSetter || method.IsGetter)));
+ }
+
+
+
private HashSet<HashSet<MemberReference>> MergeBlocks(IEnumerable<HashSet<MemberReference>> memberBlocks)
{
var inputBlocks = new HashSet<HashSet<MemberReference>>(memberBlocks);
@@ -152,5 +184,10 @@ private bool ShouldBeRemoved(MemberReference reference)
var method = reference as MethodDefinition;
return method!=null && (!method.HasBody || method.IsGeneratedCode());
}
+
+ private bool IsEmpty(MethodDefinition method)
+ {
+ return !(method.HasBody && method.Body.Instructions.Count(inst => inst.OpCode.Code != Code.Ret && inst.OpCode.Code != Code.Nop) > 0);
+ }
}
}
View
280 DependencyParser/Program.cs
@@ -23,17 +23,28 @@ public class Program
private static readonly DethOfInheritanceTreeAnalyzer ditAnalyzer = new DethOfInheritanceTreeAnalyzer();
+ private static readonly DependencyAnalyzer dependencyAnalyzer = new DependencyAnalyzer();
+
+ private static readonly ArchitectureRuleEngine architectureRuleEngine = new ArchitectureRuleEngine();
+
+
+ private static bool designAnalysis = false;
+
public static void Main(string[] args)
{
bool showHelp = false;
string assemblyName = "ASSEMBLY.DLL";
string outputPath = "output.xml";
+ IEnumerable<string> ignorableFieldNames = null;
- var p = new OptionSet()
+ var p = new OptionSet()
{
{ "a|assembly=", "the name of the assembly to scan", v => assemblyName = v },
{ "o|output=", "the path to the output XML", v => outputPath = v },
{ "h|help", "show this message and exit", v => showHelp = v != null },
+ { "d|design", "flag that enables design analysis", d => designAnalysis = d != null },
+ { "r|rules=", "Architecture rules comma separated", r => architectureRuleEngine.Init(r) },
+ { "i|ignorable_fields=", "When design analysis is enabled, comma list of names of fields that should not be taken in account for LCOM4 analysis", list => ignorableFieldNames = list.Split(',') }
};
try
@@ -55,6 +66,11 @@ public static void Main(string[] args)
return;
}
+ if (ignorableFieldNames!=null)
+ {
+ lcom4Analyzer.IgnorableFieldNames = ignorableFieldNames;
+ }
+
var targetFolder = Path.GetDirectoryName(assemblyName);
using (var stream = new FileStream(outputPath, FileMode.Create))
@@ -170,13 +186,11 @@ static void Analysis(XmlTextWriter writer, ModuleDefinition module, string fullP
}
writer.WriteEndElement();
-
- writer.WriteStartElement("Design");
- foreach (var t in module.Types) {
- GenerateTypeDesignMeasures(writer, t);
+
+ if (designAnalysis)
+ {
+ GenerateTypeDesignMeasures(writer, module);
}
- writer.WriteEndElement();
-
}
writer.WriteEndElement();
@@ -189,168 +203,108 @@ static void Analysis(XmlTextWriter writer, ModuleDefinition module, string fullP
Parsed.Add(module.Assembly.Name.FullName);
}
- public static void GenerateTypeDesignMeasures(XmlTextWriter writer, TypeDefinition t)
+ public static void GenerateTypeDesignMeasures(XmlTextWriter writer, ModuleDefinition module)
{
- var designWriter = new DesignMeasuresWriter() { Xml = writer, Type = t};
- designWriter.Lcom4Blocks = lcom4Analyzer.FindLcomBlocks(t);
- designWriter.ResponseForClass = rfcAnalyzer.ComputeRFC(t);
- designWriter.DethOfInheritance = ditAnalyzer.ComputeDIT(t);
-
- designWriter.Write();
- }
-
- public static void ParseType(XmlTextWriter writer, TypeDefinition t)
- {
- // ignore generated types
- if (t.DeclaringType == null && t.Namespace.Equals(string.Empty))
- {
- return;
- }
-
- if (t.Name.StartsWith("<>"))
- {
- return;
- }
-
- foreach (var n in t.NestedTypes)
- {
- ParseType(writer, n);
- }
-
- Dictionary<string, IList<string>> cache = new Dictionary<string, IList<string>>();
- writer.WriteStartElement("From");
- writer.WriteAttributeString("fullname", t.FullName);
-
- foreach (var c in t.CustomAttributes)
- {
- AddDependency(writer, cache, t, c.AttributeType);
- }
-
- if (t.BaseType != null)
- {
- AddDependency(writer, cache, t, t.BaseType);
- }
-
- foreach (var i in t.Interfaces)
- {
- AddDependency(writer, cache, t, i);
- }
-
- foreach (var e in t.Events)
- {
- AddDependency(writer, cache, t, e.EventType);
- }
-
- foreach (var f in t.Fields)
- {
- AddDependency(writer, cache, t, f.FieldType);
- }
-
- foreach (var p in t.Properties)
- {
- AddDependency(writer, cache, t, p.PropertyType);
- }
-
- foreach (var m in t.Methods)
- {
- AddDependency(writer, cache, t, m.ReturnType);
-
- foreach (var p in m.Parameters)
- {
- AddDependency(writer, cache, t, p.ParameterType);
- }
-
- if (m.Body != null)
- {
- //m.Body.Instructions[0].SequencePoint.Document
-
- foreach (var v in m.Body.Variables)
- {
- AddDependency(writer, cache, t, v.VariableType);
- }
-
- foreach (var e in m.Body.ExceptionHandlers)
- {
- if (e.CatchType != null)
- {
- AddDependency(writer, cache, t, e.CatchType);
- }
- }
- }
- }
-
- writer.WriteEndElement();
- }
-
- public static void AddDependency(XmlTextWriter writer, IDictionary<string, IList<string>> cache, TypeDefinition from, TypeReference to)
- {
- if (from.FullName.Equals(to.FullName))
- {
- return;
- }
-
- // ignore generic parameters
- if (to.IsGenericParameter)
- {
- return;
- }
-
- // ignore generated types, without namespace
- if (to.Namespace.Equals(string.Empty))
- {
- return;
- }
-
- if (to.IsArray)
- {
- to = to.GetElementType();
- }
-
- if (to.IsGenericInstance)
- {
- var generic = (GenericInstanceType)to;
- foreach (var a in generic.GenericArguments)
- {
- AddDependency(writer, cache, from, a);
- }
- to = to.GetElementType();
- }
+ writer.WriteStartElement("Design");
+ var sourceRegistry = new SourceRegistry(module);
+
+ // first generate and write measures for types colocated in the same files
+ GenerateMultiTypeDesignMeasures(writer, module, sourceRegistry);
+
+ // then deal with type with source locations not treated before
+ foreach (var t in module.Types)
+ {
+ var path = t.GetSourcePath();
+ if (path!=null && !sourceRegistry.IsMultiTypeFile(path))
+ {
+ DesignMeasures measures = GenerateTypeMeasures(t);
+ measures.Write(writer);
+ }
+ }
- // ignore types from .Net framework
- if (to.Scope.Name.Equals("mscorlib") || to.Scope.Name.StartsWith("System") || to.Scope.Name.StartsWith("Microsoft"))
- {
- return;
- }
+ // and at last deal with types without source location
+ foreach (var t in module.Types) {
+ var path = t.GetSourcePath();
+ if (path == null) {
+ var measures = GenerateTypeMeasures(t);
+ measures.Write(writer);
+ }
+ }
- IList<string> toList;
- if (!cache.TryGetValue(from.FullName, out toList))
- {
- toList = new List<string>();
- cache.Add(from.FullName, toList);
- }
+ writer.WriteEndElement();
+ }
- if (toList.Contains(to.FullName))
- {
- return;
- }
+ private static DesignMeasures GenerateTypeMeasures(TypeDefinition t)
+ {
+ return new DesignMeasures {
+ Type = t,
+ Lcom4Blocks = lcom4Analyzer.FindLcomBlocks(t),
+ ResponseForClass = rfcAnalyzer.ComputeRFC(t),
+ DethOfInheritance = ditAnalyzer.ComputeDIT(t)
+ };
+ }
-
- writer.WriteStartElement("To");
- writer.WriteAttributeString("fullname", to.FullName);
- if (to.Scope is ModuleDefinition)
- {
- writer.WriteAttributeString("assemblyname", ((ModuleDefinition)to.Scope).Assembly.Name.Name);
- writer.WriteAttributeString("assemblyversion", ((ModuleDefinition)to.Scope).Assembly.Name.Version.ToString());
- }
- else if(to.Scope is AssemblyNameReference)
- {
- writer.WriteAttributeString("assemblyname", ((AssemblyNameReference)to.Scope).Name);
- writer.WriteAttributeString("assemblyversion", ((AssemblyNameReference)to.Scope).Version.ToString());
- }
+ private static void GenerateMultiTypeDesignMeasures(XmlTextWriter writer, ModuleDefinition module, SourceRegistry sourceRegistry)
+ {
+ var multiTypeMeasures = new Dictionary<string, DesignMeasures>();
+ foreach (var t in module.Types) {
+
+ var path = t.GetSourcePath();
+
+ if (sourceRegistry.IsMultiTypeFile(path)) {
+ var measures = GenerateTypeMeasures(t);
+ if (multiTypeMeasures.ContainsKey(path)) {
+ multiTypeMeasures[path] = multiTypeMeasures[path].Merge(measures);
+ } else {
+ multiTypeMeasures[path] = measures;
+ }
+ }
+ }
+ foreach (var multiTypeMeasure in multiTypeMeasures.Values) {
+ multiTypeMeasure.Write(writer);
+ }
+ }
- writer.WriteEndElement();
+ public static void ParseType(XmlTextWriter writer, TypeDefinition t)
+ {
+ var dependencies = dependencyAnalyzer.FindTypeDependencies(t);
+
+ if (dependencies != null && dependencies.Count() > 0) {
+ var filteredDependencies = dependencyAnalyzer.FilterSystemDependencies(dependencies);
+ if (filteredDependencies.Count() > 0)
+ {
+ writer.WriteStartElement("From");
+ writer.WriteAttributeString("fullname", t.FullName);
+
+ var path = t.GetSourcePath();
+ if (path != null)
+ {
+ writer.WriteAttributeString("path", path);
+ }
+
+ foreach (var to in filteredDependencies) {
+ writer.WriteStartElement("To");
+ writer.WriteAttributeString("fullname", to.FullName);
+
+ if (to.Scope is ModuleDefinition) {
+ writer.WriteAttributeString("assemblyname", ((ModuleDefinition)to.Scope).Assembly.Name.Name);
+ writer.WriteAttributeString("assemblyversion", ((ModuleDefinition)to.Scope).Assembly.Name.Version.ToString());
+ } else if (to.Scope is AssemblyNameReference) {
+ writer.WriteAttributeString("assemblyname", ((AssemblyNameReference)to.Scope).Name);
+ writer.WriteAttributeString("assemblyversion", ((AssemblyNameReference)to.Scope).Version.ToString());
+ }
+
+ writer.WriteEndElement();
+ }
+ writer.WriteEndElement();
+ }
+ var violations = architectureRuleEngine.FindArchitectureViolation(t, dependencies);
+ foreach (var violation in violations)
+ {
+ violation.Write(writer);
+ }
+ }
+ }
- toList.Add(to.FullName);
- }
}
}
View
2  DependencyParser/ResponseForClassAnalyzer.cs
@@ -58,7 +58,7 @@ public int ComputeRFC(TypeDefinition t)
private bool NeedToBeFiltered(TypeDefinition typeDefinition, MethodReference method)
{
- try
+ try
{
return method.IsGeneratedCode();
} catch(AssemblyResolutionException)
View
57 DependencyParser/SourceHelper.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+namespace DependencyParser {
+
+ public static class SourceHelper {
+
+ public static string GetSourcePath(this TypeDefinition self)
+ {
+ var firstInstruction = ExtractFirst(self);
+ string result = null;
+ if (firstInstruction!=null)
+ {
+ result = firstInstruction.SequencePoint.Document.Url;
+ }
+ return result;
+ }
+
+ /// <summary>
+ /// Copy/pasted from gendarme Symbols class
+ /// </summary>
+ /// <param name="type"></param>
+ /// <returns></returns>
+ private static Instruction ExtractFirst(TypeDefinition type)
+ {
+ if (type == null)
+ return null;
+ foreach (MethodDefinition method in type.Methods) {
+ Instruction ins = ExtractFirst(method);
+ if (ins != null)
+ return ins;
+ }
+ return null;
+ }
+
+ /// <summary>
+ /// Copy/pasted from gendarme Symbols class
+ /// </summary>
+ /// <param name="method"></param>
+ /// <returns></returns>
+ private static Instruction ExtractFirst(MethodDefinition method)
+ {
+ if ((method == null) || !method.HasBody || method.Body.Instructions.Count == 0)
+ return null;
+ Instruction ins = method.Body.Instructions[0];
+ // note that the first instruction often does not have a sequence point
+ while (ins != null && ins.SequencePoint == null)
+ ins = ins.Next;
+
+ return (ins != null && ins.SequencePoint != null) ? ins : null;
+ }
+ }
+}
View
58 DependencyParser/SourceRegistry.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Mono.Cecil;
+
+namespace DependencyParser {
+
+
+ /// <summary>
+ /// TODO: Update summary.
+ /// </summary>
+ public class SourceRegistry {
+
+ private HashSet<string> multiTypeSourceFiles = new HashSet<string>();
+
+ public SourceRegistry(ModuleDefinition module)
+ {
+ var registry = new Dictionary<string, IEnumerable<TypeDefinition>>();
+ foreach (var t in module.Types) {
+ var path = t.GetSourcePath();
+ if (path == null)
+ {
+ continue;
+ }
+ if (registry.ContainsKey(path))
+ {
+ var types = registry[path];
+ if (types is List<TypeDefinition>)
+ {
+ ((List<TypeDefinition>)types).Add(t);
+ } else
+ {
+ var typeList = new List<TypeDefinition>(types) {t};
+ registry.Remove(path);
+ registry.Add(path, typeList);
+ }
+ } else
+ {
+ registry.Add(path, new TypeDefinition[] { t });
+ }
+ }
+ foreach (var pair in registry.Where(entry => entry.Value.Count() > 1))
+ {
+ multiTypeSourceFiles.Add(pair.Key);
+ }
+ }
+
+ public bool IsMultiTypeFile(string path)
+ {
+
+ return path==null ? false : multiTypeSourceFiles.Contains(path);
+ }
+
+
+
+ }
+}
View
48 DependencyParser/WildcardPatternMatcher.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace DependencyParser {
+
+ /// <summary>
+ /// Copy/paste from http://www.codeproject.com/Tips/57304/Use-wildcard-characters-and-to-compare-strings
+ ///
+ /// </summary>
+ public static class WildcardPatternMatcher {
+
+ public static Boolean MatchWildcardString(String pattern, String input)
+ {
+ if (String.Compare(pattern, input) == 0) {
+ return true;
+ } else if (String.IsNullOrEmpty(input)) {
+ if (String.IsNullOrEmpty(pattern.Trim(new Char[1] { '*' }))) {
+ return true;
+ } else {
+ return false;
+ }
+ } else if (pattern.Length == 0) {
+ return false;
+ } else if (pattern[0] == '?') {
+ return MatchWildcardString(pattern.Substring(1), input.Substring(1));
+ } else if (pattern[pattern.Length - 1] == '?') {
+ return MatchWildcardString(pattern.Substring(0, pattern.Length - 1), input.Substring(0, input.Length - 1));
+ } else if (pattern[0] == '*') {
+ if (MatchWildcardString(pattern.Substring(1), input)) {
+ return true;
+ } else {
+ return MatchWildcardString(pattern, input.Substring(1));
+ }
+ } else if (pattern[pattern.Length - 1] == '*') {
+ if (MatchWildcardString(pattern.Substring(0, pattern.Length - 1), input)) {
+ return true;
+ } else {
+ return MatchWildcardString(pattern, input.Substring(0, input.Length - 1));
+ }
+ } else if (pattern[0] == input[0]) {
+ return MatchWildcardString(pattern.Substring(1), input.Substring(1));
+ }
+ return false;
+ }
+ }
+}
View
2  pom.xml
@@ -10,6 +10,8 @@
<visual.studio.solution>DependencyParser.sln</visual.studio.solution>
<dotnet.tool.version>4.0</dotnet.tool.version>
<visual.test.project.pattern>*.Tests;*Test</visual.test.project.pattern>
+ <sonar.language>cs</sonar.language>
+ <sonar.dotnet.assemblies>bin/Debug/$(AssemblyName).$(OutputType)</sonar.dotnet.assemblies>
</properties>
<build>
<plugins>
Please sign in to comment.
Something went wrong with that request. Please try again.