Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fix bugs & add source locations in xml reports

  • Loading branch information...
commit 4fbc1a81d86641e2fc460fedeeb6f22c92d19dbf 1 parent c381b1f
Alexandre Victoor alexvictoor authored
9 DependencyParser.Test/DependencyParser.Test.csproj
View
@@ -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>
@@ -55,7 +59,7 @@
<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" />
</ItemGroup>
@@ -69,6 +73,9 @@
<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>
4 DependencyParser.Test/DependencyParserTest.cs
View
@@ -14,7 +14,9 @@ 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" });
+ string assemblyPath = @"C:\work\temp\springdotnet\trunk\build\VS.Net.2010\Spring.Core\Debug\Spring.Core.dll";
+ //Program.Main(new string[] { "-a=testdata/Example.Core.dll", "-o=test.xml" });
+ Program.Main(new string[] { "-a="+assemblyPath, "-o=test-spring.xml" });
string expected = File.ReadAllText("testdata/test.xml");
string result = File.ReadAllText("test.xml");
7 DependencyParser.Test/DesignMeasuresWriterTest.cs → DependencyParser.Test/DesignMeasuresTest.cs
View
@@ -11,7 +11,7 @@
namespace DependencyParser.Test {
[NUnit.Framework.TestFixture]
- public class DesignMeasuresWriterTest {
+ public class DesignMeasuresTest {
[Test]
public void Should_Generate()
{
@@ -21,7 +21,7 @@ public void Should_Generate()
using (var writer = new XmlTextWriter(stream, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
- var designWriter = new DesignMeasuresWriter();
+ var designWriter = new DesignMeasures();
var typeDefinition = getType("DependencyParser.Test.SimpleClassWithTwoFields");
var blocks = new HashSet<HashSet<MemberReference>>();
@@ -30,13 +30,12 @@ public void Should_Generate()
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();
+ designWriter.Write(writer);
}
}
string expected = File.ReadAllText("lcom4-expected.xml");
118 DependencyParser.Test/Lcom4AnalyzerTest.cs
View
@@ -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;
@@ -20,6 +23,14 @@ public void Should_Find_One_Block_On_A_Simple_Class()
}
[Test]
+ public void Should_Find_Two_Blocks_On_A_Simple_Stateless_Class()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.SimpleCalculator"));
+ Assert.AreEqual(2, blocks.Count);
+ }
+
+ [Test]
public void Should_Find_One_Block_On_A_Class_With_Strong_Cohesion()
{
var analyzer = new Lcom4Analyzer();
@@ -69,6 +80,22 @@ public void Should_Take_In_Account_Calls_To_Abstract_Methods()
}
[Test]
+ public void Should_Ignore_Empty_Methods()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.ClassWithEmptyMethods"));
+ Assert.AreEqual(0, blocks.Count);
+ }
+
+ [Test]
+ public void Should_Take_In_Account_Calls_Ignore_Empty_Methods()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(GetType("DependencyParser.Test.ClassWithCallsToEmptyMethods"));
+ Assert.AreEqual(1, blocks.Count);
+ }
+
+ [Test]
public void Should_Ignore_Constructors()
{
var analyzer = new Lcom4Analyzer();
@@ -142,6 +169,28 @@ public void Should_Not_Take_In_Account_Static_Methods()
Assert.AreEqual(1, blocks.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);
+ }
+
+ [Test]
+ public void Should_Ignore_Empty_Virtual_Methods()
+ {
+ 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);
+ }
+
private TypeDefinition GetType(string name)
{
string unit = Assembly.GetExecutingAssembly().Location;
@@ -175,6 +224,19 @@ public void Decrement()
}
}
+ 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 +374,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 +535,28 @@ public static SimpleClassWithStaticMethod create()
return new SimpleClassWithStaticMethod();
}
}
+
+ public abstract class ClassWithExternalCalls {
+ /// <summary>
+ /// Inner storage for ReadOnlyDictionary
+ /// </summary>
+ private readonly Thread _thread;
+
+ public ClassWithExternalCalls(Thread thread)
+ {
+ thread = _thread;
+ }
+
+
+ public void Stop()
+ {
+ _thread.Abort();
+ }
+
+ public void Start()
+ {
+ _thread.Start();
+ }
+
+ }
}
2  DependencyParser.Test/lcom4-expected.xml
View
@@ -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()" />
BIN  DependencyParser.Test/testdata/external/Spring.Core.dll
View
Binary file not shown
13 DependencyParser.Test/testdata/test.xml
View
@@ -25,10 +25,7 @@
<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" 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 +44,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 +63,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 +74,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>
4 DependencyParser/DependencyParser.csproj
View
@@ -80,10 +80,12 @@
<ItemGroup>
<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" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
40 DependencyParser/DesignMeasuresWriter.cs → DependencyParser/DesignMeasures.cs
View
@@ -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,32 @@ public class DesignMeasuresWriter {
public IEnumerable<IEnumerable<MemberReference>> Lcom4Blocks { set; private get; }
- public void Write()
+
+ public DesignMeasures Merge(DesignMeasures measures)
+ {
+ return new DesignMeasures() {
+ Type = Type,
+ 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);
- Xml.WriteAttributeString("rfc", ResponseForClass.ToString());
- Xml.WriteAttributeString("dit", DethOfInheritance.ToString());
+ xml.WriteStartElement("type");
+ xml.WriteAttributeString("fullName", Type.FullName);
+ 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 +61,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)
11 DependencyParser/Lcom4Analyzer.cs
View
@@ -21,7 +21,7 @@ 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;
}
@@ -47,6 +47,10 @@ public HashSet<HashSet<MemberReference>> FindLcomBlocks(TypeDefinition t)
{
case OperandType.InlineField:
FieldDefinition fd = inst.Operand as FieldDefinition;
+ if (fd == null && inst.Operand is FieldReference)
+ {
+ fd = ((FieldReference) inst.Operand).Resolve();
+ }
if (null != fd && (!fd.IsGeneratedCode() || method.IsSetter || method.IsGetter))
{
mr = fd;
@@ -152,5 +156,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);
+ }
}
}
74 DependencyParser/Program.cs
View
@@ -170,12 +170,8 @@ static void Analysis(XmlTextWriter writer, ModuleDefinition module, string fullP
}
writer.WriteEndElement();
-
- writer.WriteStartElement("Design");
- foreach (var t in module.Types) {
- GenerateTypeDesignMeasures(writer, t);
- }
- writer.WriteEndElement();
+
+ GenerateTypeDesignMeasures(writer, module);
}
@@ -189,17 +185,69 @@ 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)
+ {
+ 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);
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ writer.WriteEndElement();
+ }
+
+ private static DesignMeasures GenerateTypeMeasures(TypeDefinition t)
{
- var designWriter = new DesignMeasuresWriter() { Xml = writer, Type = t};
- designWriter.Lcom4Blocks = lcom4Analyzer.FindLcomBlocks(t);
- designWriter.ResponseForClass = rfcAnalyzer.ComputeRFC(t);
- designWriter.DethOfInheritance = ditAnalyzer.ComputeDIT(t);
+ return new DesignMeasures {
+ Type = t,
+ Lcom4Blocks = lcom4Analyzer.FindLcomBlocks(t),
+ ResponseForClass = rfcAnalyzer.ComputeRFC(t),
+ DethOfInheritance = ditAnalyzer.ComputeDIT(t)
+ };
+ }
- designWriter.Write();
+ 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);
+ }
}
- public static void ParseType(XmlTextWriter writer, TypeDefinition t)
+ public static void ParseType(XmlTextWriter writer, TypeDefinition t)
{
// ignore generated types
if (t.DeclaringType == null && t.Namespace.Equals(string.Empty))
2  DependencyParser/ResponseForClassAnalyzer.cs
View
@@ -58,7 +58,7 @@ public int ComputeRFC(TypeDefinition t)
private bool NeedToBeFiltered(TypeDefinition typeDefinition, MethodReference method)
{
- try
+ try
{
return method.IsGeneratedCode();
} catch(AssemblyResolutionException)
57 DependencyParser/SourceHelper.cs
View
@@ -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;
+ }
+ }
+}
58 DependencyParser/SourceRegistry.cs
View
@@ -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);
+ }
+
+
+
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.