Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Proper handling of properties and abstract methods

  • Loading branch information...
commit 51e89bead7ce21ef65103a78c5a6c5fe1281cc1d 1 parent 7593e4f
@alexvictoor alexvictoor authored
View
2  DependencyParser.Test/DependencyParser.Test.csproj
@@ -34,6 +34,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
+ <PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Cecil">
@@ -68,6 +69,7 @@
</Content>
<Content Include="testdata\test.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
+ <SubType>Designer</SubType>
</Content>
</ItemGroup>
<ItemGroup>
View
155 DependencyParser.Test/Lcom4AnalyzerTest.cs
@@ -29,14 +29,46 @@ public void Should_Find_One_Block_On_A_Class_With_Strong_Cohesion()
}
[Test]
- public void Should_Treat_Accessors_As_Normal_Methods()
+ public void Should_Ignore_Simple_Accessors()
{
var analyzer = new Lcom4Analyzer();
var blocks = analyzer.FindLcomBlocks(getType("DependencyParser.Test.ClassWithProperties"));
+ Assert.AreEqual(0, blocks.Count);
+ }
+
+ [Test]
+ public void Should_Take_In_Account_Calls_To_Simple_Accessors()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(getType("DependencyParser.Test.ClassWithCallsToAccessors"));
+ Assert.AreEqual(1, blocks.Count);
+ }
+
+ [Test]
+ public void Should_Take_In_Account_Complex_Accessors()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(getType("DependencyParser.Test.ClassWithComplexProperties"));
Assert.AreEqual(2, blocks.Count);
}
[Test]
+ public void Should_Ignore_Abstract_Methods()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(getType("DependencyParser.Test.AbstractClass"));
+ Assert.AreEqual(1, blocks.Count);
+ }
+
+ [Test]
+ public void Should_Take_In_Account_Calls_To_Abstract_Methods()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(getType("DependencyParser.Test.AbstractTemplateClass"));
+ Assert.AreEqual(1, blocks.Count);
+ }
+
+ [Test]
public void Should_Ignore_Constructors()
{
var analyzer = new Lcom4Analyzer();
@@ -45,7 +77,7 @@ public void Should_Ignore_Constructors()
}
[Test]
- public void Should_Not_Fail__On_Empty_Interface()
+ public void Should_Not_Fail_On_Empty_Interface()
{
var analyzer = new Lcom4Analyzer();
var blocks = analyzer.FindLcomBlocks(getType("DependencyParser.Test.IEmpty"));
@@ -53,7 +85,7 @@ public void Should_Not_Fail__On_Empty_Interface()
}
[Test]
- public void Should_Not_Fail__On_Empty_Class()
+ public void Should_Not_Fail_On_Empty_Class()
{
var analyzer = new Lcom4Analyzer();
var blocks = analyzer.FindLcomBlocks(getType("DependencyParser.Test.EmptyClass"));
@@ -102,6 +134,14 @@ public void Should_Not_Take_In_Account_Inherited_Methods()
Assert.AreEqual(3, blocks.ElementAt(0).Count);
}
+ [Test]
+ public void Should_Not_Take_In_Account_Static_Methods()
+ {
+ var analyzer = new Lcom4Analyzer();
+ var blocks = analyzer.FindLcomBlocks(getType("DependencyParser.Test.SimpleClassWithStaticMethod"));
+ Assert.AreEqual(1, blocks.Count);
+ }
+
private TypeDefinition getType(string name)
{
string unit = Assembly.GetExecutingAssembly().Location;
@@ -176,12 +216,12 @@ public void doC()
Bar();
}
- private static void Foo()
+ private void Foo()
{
Console.WriteLine("Whatever".ToLower());
}
- private static void Bar()
+ private void Bar()
{
Console.WriteLine("Whatever");
}
@@ -193,9 +233,88 @@ public class ClassWithProperties {
public string Name { get; set; }
}
+ public class ClassWithCallsToAccessors {
+ public int Counter { get; set; }
+
+ public string Name { get; set; }
+
+ public void Foo()
+ {
+ Console.WriteLine("Whatever " + Counter + " " + Name);
+ }
+
+ public void Bar()
+ {
+ Console.WriteLine("Whatever " + Counter + " " + Name);
+ }
+ }
+
+ public class ClassWithComplexProperties {
+ private int counter;
+
+ public int Counter
+ {
+ get
+ {
+ if (counter>10)
+ {
+ return 10;
+ }
+ return counter;
+ }
+
+ set
+ {
+ counter = value;
+ }
+ }
+
+ public void Foo()
+ {
+ Console.WriteLine("Whatever".ToLower());
+ }
+ }
+
+ public abstract class AbstractClass {
+
+ public void DoA()
+ {
+ Foo();
+ }
+
+ public void DoB()
+ {
+ Foo();
+ }
+
+
+ public abstract void DoC();
+
+ private void Foo()
+ {
+ Console.WriteLine("Whatever".ToLower());
+ }
+ }
+
+ public abstract class AbstractTemplateClass {
+
+ public void DoA()
+ {
+ DoC();
+ }
+
+ public void DoB()
+ {
+ DoC();
+ }
+
+ public abstract void DoC();
+
+ }
+
public class SimpleDisposableClass : IDisposable
{
- public void doSomething()
+ public void DoSomething()
{
Console.WriteLine("Whatever");
}
@@ -211,12 +330,12 @@ public class SimpleClassWithEquals {
private int fieldB;
- public void doA()
+ public void DoA()
{
fieldA++;
}
- public void doB()
+ public void DoB()
{
fieldB++;
}
@@ -302,4 +421,24 @@ public void doD()
doA();
}
}
+
+ public class SimpleClassWithStaticMethod {
+ private int counter;
+
+ public void Increment()
+ {
+ counter++;
+ }
+
+ public void Decrement()
+ {
+ counter--;
+ }
+
+ public static SimpleClassWithStaticMethod create()
+ {
+ Console.WriteLine("factory method");
+ return new SimpleClassWithStaticMethod();
+ }
+ }
}
View
4 DependencyParser.Test/Lcom4WriterTest.cs
@@ -20,12 +20,14 @@ public void Should_Generate()
{
using (var writer = new XmlTextWriter(stream, Encoding.UTF8))
{
+ writer.Formatting = Formatting.Indented;
var lcom4Writer = new Lcom4Writer();
var typeDefinition = getType("DependencyParser.Test.SimpleClassWithTwoFields");
var blocks = new HashSet<HashSet<MemberReference>>();
+
foreach (var mth in typeDefinition.Methods)
{
- var block = new HashSet<MemberReference> {mth};
+ var block = new HashSet<MemberReference> { mth, typeDefinition.Fields.First() };
blocks.Add(block);
}
lcom4Writer.Write(writer, typeDefinition, blocks);
View
15 DependencyParser.Test/lcom4-expected.xml
@@ -1 +1,14 @@
-<type fullName="DependencyParser.Test.SimpleClassWithTwoFields"><block><element type="Method" name="System.Void doA()" /></block><block><element type="Method" name="System.Void doB()" /></block><block><element type="Method" name="System.Void .ctor()" /></block></type>
+<type fullName="DependencyParser.Test.SimpleClassWithTwoFields">
+ <block>
+ <element type="Field" name="fieldA" />
+ <element type="Method" name="System.Void doA()" />
+ </block>
+ <block>
+ <element type="Field" name="fieldA" />
+ <element type="Method" name="System.Void doB()" />
+ </block>
+ <block>
+ <element type="Field" name="fieldA" />
+ <element type="Method" name="System.Void .ctor()" />
+ </block>
+</type>
View
40 DependencyParser.Test/testdata/test.xml
@@ -27,24 +27,14 @@
<lcom4>
<type fullName="&lt;Module&gt;" />
<type fullName="Example.Core.IMoney" />
- <type fullName="Example.Core.Model.SubType">
- <block>
- <element type="Method" name="System.String get_Name()" />
- <element type="Field" name="&lt;Name&gt;k__BackingField" />
- <element type="Method" name="System.Void set_Name(System.String)" />
- </block>
- </type>
+ <type fullName="Example.Core.Model.SubType" />
<type fullName="Example.Core.Money">
<block>
- <element type="Method" name="Example.Core.IMoney Add(Example.Core.IMoney)" />
- <element type="Method" name="Example.Core.IMoney Subtract(Example.Core.IMoney)" />
- </block>
- <block>
- <element type="Method" name="Example.Core.IMoney AddMoney(Example.Core.Money)" />
- <element type="Method" name="System.String get_Currency()" />
- <element type="Method" name="System.Int32 get_Amount()" />
<element type="Field" name="fAmount" />
<element type="Field" name="fCurrency" />
+ <element type="Method" name="Example.Core.IMoney AddMoney(Example.Core.Money)" />
+ <element type="Method" name="System.Int32 get_Amount()" />
+ <element type="Method" name="System.String get_Currency()" />
<element type="Method" name="System.Boolean get_IsZero()" />
<element type="Method" name="Example.Core.IMoney Multiply(System.Int32)" />
<element type="Method" name="Example.Core.IMoney Negate()" />
@@ -52,34 +42,36 @@
<block>
<element type="Method" name="Example.Core.IMoney AddMoneyBag(Example.Core.MoneyBag)" />
</block>
- </type>
- <type fullName="Example.Core.Alex" />
- <type fullName="Example.Core.MoneyBag">
<block>
<element type="Method" name="Example.Core.IMoney Add(Example.Core.IMoney)" />
<element type="Method" name="Example.Core.IMoney Subtract(Example.Core.IMoney)" />
</block>
+ </type>
+ <type fullName="Example.Core.Alex" />
+ <type fullName="Example.Core.MoneyBag">
<block>
+ <element type="Field" name="fMonies" />
<element type="Method" name="Example.Core.IMoney AddMoney(Example.Core.Money)" />
- <element type="Method" name="Example.Core.IMoney Simplify()" />
<element type="Method" name="Example.Core.IMoney AddMoneyBag(Example.Core.MoneyBag)" />
<element type="Method" name="System.Void AppendBag(Example.Core.MoneyBag)" />
- <element type="Field" name="fMonies" />
<element type="Method" name="System.Void AppendMoney(Example.Core.Money)" />
- <element type="Method" name="Example.Core.Money FindMoney(System.String)" />
<element type="Method" name="System.Boolean Contains(Example.Core.Money)" />
+ <element type="Method" name="Example.Core.Money FindMoney(System.String)" />
<element type="Method" name="System.Boolean get_IsZero()" />
<element type="Method" name="Example.Core.IMoney Multiply(System.Int32)" />
<element type="Method" name="Example.Core.IMoney Negate()" />
+ <element type="Method" name="Example.Core.IMoney Simplify()" />
+ </block>
+ <block>
+ <element type="Method" name="Example.Core.IMoney Add(Example.Core.IMoney)" />
+ <element type="Method" name="Example.Core.IMoney Subtract(Example.Core.IMoney)" />
</block>
</type>
<type fullName="Example.Core.SampleMeasure">
<block>
- <element type="Method" name="System.Int32 get_Size()" />
- <element type="Field" name="&lt;Size&gt;k__BackingField" />
- <element type="Method" name="System.Void set_Size(System.Int32)" />
- <element type="Method" name="System.String Compute()" />
+ <element type="Field" name="Size (property)" />
<element type="Field" name="actual" />
+ <element type="Method" name="System.String Compute()" />
<element type="Method" name="System.String Compute(System.String)" />
</block>
<block>
View
16 DependencyParser.sln
@@ -28,10 +28,10 @@ Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|Any CPU.ActiveCfg = Debug|x86
{AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|Any CPU.Build.0 = Debug|x86
- {AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
- {AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|Mixed Platforms.Build.0 = Debug|x86
- {AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|x86.ActiveCfg = Debug|x86
- {AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|x86.Build.0 = Debug|x86
+ {AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|x86.ActiveCfg = Release|x86
+ {AE47EF64-9F61-463A-9088-79907968A1FE}.Debug|x86.Build.0 = Release|x86
{AE47EF64-9F61-463A-9088-79907968A1FE}.Release|Any CPU.ActiveCfg = Release|x86
{AE47EF64-9F61-463A-9088-79907968A1FE}.Release|Any CPU.Build.0 = Release|x86
{AE47EF64-9F61-463A-9088-79907968A1FE}.Release|Mixed Platforms.ActiveCfg = Release|x86
@@ -40,10 +40,10 @@ Global
{AE47EF64-9F61-463A-9088-79907968A1FE}.Release|x86.Build.0 = Release|x86
{F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
- {F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
- {F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|x86.ActiveCfg = Debug|Any CPU
- {F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|x86.Build.0 = Debug|Any CPU
+ {F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|x86.ActiveCfg = Release|Any CPU
+ {F2D48840-011D-40D3-8CE9-6796A2FB237F}.Debug|x86.Build.0 = Release|Any CPU
{F2D48840-011D-40D3-8CE9-6796A2FB237F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F2D48840-011D-40D3-8CE9-6796A2FB237F}.Release|Any CPU.Build.0 = Release|Any CPU
{F2D48840-011D-40D3-8CE9-6796A2FB237F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
View
81 DependencyParser/Lcom4Analyzer.cs
@@ -37,53 +37,56 @@ public HashSet<HashSet<MemberReference>> FindLcomBlocks(TypeDefinition t)
memberBlocks[method] = currentBlock;
}
-
- foreach ( Instruction inst in method.Body.Instructions)
+ if (method.HasBody)
{
- MemberReference mr = null;
-
- switch (inst.OpCode.OperandType) {
- case OperandType.InlineField:
- FieldDefinition fd = inst.Operand as FieldDefinition;
- if (null != fd && (!fd.IsGeneratedCode() || method.IsSetter || method.IsGetter))
- {
- mr = fd;
- }
- break;
- case OperandType.InlineMethod:
- // special case for automatic properties since the 'backing' fields won't be used
- MethodDefinition md = inst.Operand as MethodDefinition;
- if (md != null && !NeedToBeFiltered(t, md))
- {
- mr = md;
- }
- break;
- }
- if (mr!=null && !currentBlock.Contains(mr))
+ foreach (Instruction inst in method.Body.Instructions)
{
+ MemberReference mr = null;
- if (memberBlocks.ContainsKey(mr))
+ switch (inst.OpCode.OperandType)
{
- memberBlocks[mr].UnionWith(currentBlock);
- currentBlock = memberBlocks[mr];
+ case OperandType.InlineField:
+ FieldDefinition fd = inst.Operand as FieldDefinition;
+ if (null != fd && (!fd.IsGeneratedCode() || method.IsSetter || method.IsGetter))
+ {
+ mr = fd;
+ }
+ break;
+ case OperandType.InlineMethod:
+ // special case for automatic properties since the 'backing' fields won't be used
+ MethodDefinition md = inst.Operand as MethodDefinition;
+ if (md != null && !NeedToBeFiltered(t, md))
+ {
+ mr = md;
+ }
+ break;
}
- else
+ if (mr != null && !currentBlock.Contains(mr))
{
- currentBlock.Add(mr);
- memberBlocks[mr] = currentBlock;
+
+ if (memberBlocks.ContainsKey(mr))
+ {
+ memberBlocks[mr].UnionWith(currentBlock);
+ currentBlock = memberBlocks[mr];
+ }
+ else
+ {
+ currentBlock.Add(mr);
+ memberBlocks[mr] = currentBlock;
+ }
}
}
}
memberBlocks[method] = currentBlock;
}
- return MergeBlocks(memberBlocks.Values);
+ return CleanBlocks(MergeBlocks(memberBlocks.Values));
}
private bool NeedToBeFiltered(TypeDefinition t, MethodDefinition method)
{
return (!t.AllSuperTypes().Contains(method.DeclaringType)
- ||!method.HasBody
+ || method.IsStatic
|| (method.IsGeneratedCode() && !method.IsSetter && !method.IsGetter)
|| method.IsConstructor
|| (method.IsSpecialName && !method.IsSetter && !method.IsGetter)
@@ -94,7 +97,6 @@ private bool NeedToBeFiltered(TypeDefinition t, MethodDefinition method)
}
-
private HashSet<HashSet<MemberReference>> MergeBlocks(IEnumerable<HashSet<MemberReference>> memberBlocks)
{
var inputBlocks = new HashSet<HashSet<MemberReference>>(memberBlocks);
@@ -133,5 +135,22 @@ private bool CanBeMerged(HashSet<MemberReference> block, HashSet<MemberReference
var intersection = block.Intersect(mergeCandidate);
return intersection.Count() > 0;
}
+
+ private HashSet<HashSet<MemberReference>> CleanBlocks(HashSet<HashSet<MemberReference>> blocks)
+ {
+ foreach (var block in blocks)
+ {
+ block.RemoveWhere(ShouldBeRemoved);
+ }
+
+ blocks.RemoveWhere(b => b.Count(member => member is MethodDefinition) == 0);
+ return blocks;
+ }
+
+ private bool ShouldBeRemoved(MemberReference reference)
+ {
+ var method = reference as MethodDefinition;
+ return method!=null && (!method.HasBody || method.IsGeneratedCode());
+ }
}
}
View
37 DependencyParser/Lcom4Writer.cs
@@ -13,14 +13,18 @@ namespace DependencyParser {
/// </summary>
public class Lcom4Writer {
+ private readonly ReferenceComparer comparer = new ReferenceComparer();
+
public void Write(XmlTextWriter xml, TypeDefinition type, IEnumerable<IEnumerable<MemberReference>> blocks)
{
xml.WriteStartElement("type");
xml.WriteAttributeString("fullName", type.FullName);
foreach (var block in blocks)
{
+ var orderedBlock = block.OrderBy(x => x, comparer);
+
xml.WriteStartElement("block");
- foreach (var memberReference in block)
+ foreach (var memberReference in orderedBlock)
{
xml.WriteStartElement("element");
var method = memberReference as MethodDefinition;
@@ -29,7 +33,7 @@ public void Write(XmlTextWriter xml, TypeDefinition type, IEnumerable<IEnumerabl
if (method == null)
{
elementType = "Field";
- name = memberReference.Name;
+ name = BuildFieldName(memberReference as FieldDefinition);
} else
{
elementType = "Method";
@@ -44,6 +48,19 @@ public void Write(XmlTextWriter xml, TypeDefinition type, IEnumerable<IEnumerabl
xml.WriteEndElement();
}
+ private string BuildFieldName(FieldDefinition field)
+ {
+ string name;
+ if (field.Name.Contains("BackingField"))
+ {
+ name = field.Name.Split(new string[] { "<", ">" }, StringSplitOptions.RemoveEmptyEntries)[0] + " (property)";
+ } else
+ {
+ name = field.Name;
+ }
+ return name;
+ }
+
private string BuildSignature(MethodDefinition mth)
{
var builder = new StringBuilder();
@@ -63,5 +80,21 @@ private string BuildSignature(MethodDefinition mth)
builder.Append(")");
return builder.ToString();
}
+
+ private class ReferenceComparer : IComparer<MemberReference>
+ {
+ public int Compare(MemberReference ref1, MemberReference ref2)
+ {
+ if (ref1 is MethodDefinition && ref2 is FieldDefinition)
+ {
+ return 1;
+ }
+ if (ref2 is MethodDefinition && ref1 is FieldDefinition) {
+ return -1;
+ }
+
+ return string.Compare(ref1.Name, ref2.Name);
+ }
+ }
}
}
View
5 DependencyParser/app.config
@@ -1,3 +1,6 @@
<?xml version="1.0"?>
<configuration>
-<startup><supportedRuntime version="v2.0.50727" sku="Client"/></startup></configuration>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
+ </startup>
+</configuration>
Please sign in to comment.
Something went wrong with that request. Please try again.