Skip to content

Commit

Permalink
Require stub With method in order to satisfy IntelliSense and Resharper
Browse files Browse the repository at this point in the history
  • Loading branch information
mikhailshilkov committed May 3, 2016
1 parent 32166ea commit 6d14964
Show file tree
Hide file tree
Showing 13 changed files with 109 additions and 40 deletions.
2 changes: 2 additions & 0 deletions AssemblyToProcess/AssemblyToProcess.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="NoWithStub.cs" />
<Compile Include="NoMatchingProperty.cs" />
<Compile Include="MultipleConstructors.cs" />
<Compile Include="ConstructorWithSingleArgument.cs" />
<Compile Include="NoConstructor.cs" />
<Compile Include="PropertyCasing.cs" />
<Compile Include="PropertiesOfSameType.cs" />
<Compile Include="PrimitiveValues.cs" />
</ItemGroup>
Expand Down
9 changes: 3 additions & 6 deletions AssemblyToProcess/ConstructorWithSingleArgument.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AssemblyToProcess
namespace AssemblyToProcess
{
public class ConstructorWithSingleArgument
{
Expand All @@ -15,5 +10,7 @@ public ConstructorWithSingleArgument(int value1)
public int Value1 { get; set; }

public string Value2 { get; set; }

public ConstructorWithSingleArgument With(object value) => this;
}
}
9 changes: 3 additions & 6 deletions AssemblyToProcess/MultipleConstructors.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AssemblyToProcess
namespace AssemblyToProcess
{
public class MultipleConstructors
{
Expand All @@ -21,5 +16,7 @@ public MultipleConstructors(int value1)
public int Value1 { get; set; }

public string Value2 { get; set; }

public MultipleConstructors With(object value) => this;
}
}
9 changes: 3 additions & 6 deletions AssemblyToProcess/NoConstructor.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AssemblyToProcess
namespace AssemblyToProcess
{
public class NoConstructor
{
public int Value1 { get; set; }

public string Value2 { get; set; }

public NoConstructor With(object value) => this;
}
}
9 changes: 3 additions & 6 deletions AssemblyToProcess/NoMatchingProperty.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace AssemblyToProcess
namespace AssemblyToProcess
{
public class NoMatchingProperty
{
Expand All @@ -16,5 +11,7 @@ public NoMatchingProperty(int value1, string value2, long value3)
public int Value1 { get; set; }

public string Value2 { get; set; }

public NoMatchingProperty With(object value) => this;
}
}
15 changes: 15 additions & 0 deletions AssemblyToProcess/NoWithStub.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace AssemblyToProcess
{
public class NoWithStub
{
public NoWithStub(int value1, string value2)
{
this.Value1 = value1;
this.Value2 = value2;
}

public int Value1 { get; set; }

public string Value2 { get; set; }
}
}
2 changes: 2 additions & 0 deletions AssemblyToProcess/PrimitiveValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ public PrimitiveValues(int value1, string value2, long value3)
public string Value2 { get; }

public long Value3 { get; }

public PrimitiveValues With(object value) => this;
}
}
16 changes: 12 additions & 4 deletions AssemblyToProcess/PropertiesOfSameType.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@

namespace AssemblyToProcess
{
using System;

public class PropertiesOfSameType
{
public PropertiesOfSameType(int value1, int value2, int value3)
public PropertiesOfSameType(int? value1, int? value2, int? value3)
{
this.Value1 = value1;
this.Value2 = value2;
this.Value3 = value3;
}

public int Value1 { get; }
public int? Value1 { get; }

public int? Value2 { get; }

public int? Value3 { get; }

public PropertiesOfSameType WithValue1(object value) => this;

public int Value2 { get; }
public PropertiesOfSameType WithValue2(object value) => this;

public int Value3 { get; }
public PropertiesOfSameType WithValue3(object value) => this;
}
}
18 changes: 18 additions & 0 deletions AssemblyToProcess/PropertyCasing.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

namespace AssemblyToProcess
{
public class PropertyCasing
{
public PropertyCasing(int value1, string value2)
{
this.VALUE1 = value1;
this.vaLue2 = value2;
}

public int VALUE1 { get; }

public string vaLue2 { get; }

public PropertyCasing With(object value) => this;
}
}
Binary file added Icons/package_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 27 additions & 1 deletion Tests/WeaverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ public void Setup()
[TestCase("ConstructorWithSingleArgument")]
[TestCase("MultipleConstructors")]
[TestCase("NoMatchingProperty")]
[TestCase("NoWithStub")]
public void DoesNotSatisfyAllRules_NoWithIsInjected(string typeName)
{
var type = assembly.GetType($"AssemblyToProcess.{typeName}");
Assert.False(type.GetMethods().Any(m => m.Name.StartsWith("With")));
Assert.False(type.GetMethods().Any(m => m.Name.StartsWith("With") && m.GetParameters()[0].ParameterType != typeof(object)));
}

[Test]
Expand Down Expand Up @@ -90,6 +91,31 @@ public void PropertiesOfSameType_LongNamedWithIsInjected()
Assert.AreEqual(333, result3.Value3);
}

[Test]
public void UnusualPropertyCasing_WithIsInjectedAnyway()
{
var type = assembly.GetType("AssemblyToProcess.PropertyCasing");
var instance = (dynamic)Activator.CreateInstance(type, new object[] { 1, "Hello" });

var result1 = instance.With(123);
Assert.AreEqual(123, result1.VALUE1);
Assert.AreEqual(instance.vaLue2, result1.vaLue2);

var result2 = instance.With("World");
Assert.AreEqual(instance.VALUE1, result2.VALUE1);
Assert.AreEqual("World", result2.vaLue2);
}

[Test]
public void OriginalWithMethodIsRemoved()
{
var type1 = assembly.GetType("AssemblyToProcess.PrimitiveValues");
Assert.False(type1.GetMethods().Any(m => m.Name == "With" && m.GetParameters()[0].ParameterType == typeof(object)));

var type2 = assembly.GetType("AssemblyToProcess.PropertiesOfSameType");
Assert.False(type2.GetMethods().Any(m => m.Name.StartsWith("With") && m.GetParameters()[0].ParameterType == typeof(object)));
}

#if(DEBUG)
[Test]
public void PeVerify()
Expand Down
4 changes: 2 additions & 2 deletions With.Fody/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

[assembly: AssemblyTitle("With.Fody")]
[assembly: AssemblyProduct("With.Fody")]
[assembly: AssemblyVersion("0.1.0")]
[assembly: AssemblyFileVersion("0.1.0")]
[assembly: AssemblyVersion("0.2.0")]
[assembly: AssemblyFileVersion("0.2.0")]
28 changes: 19 additions & 9 deletions With.Fody/ModuleWeaver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ public class ModuleWeaver
// An instance of Mono.Cecil.ModuleDefinition for processing
public ModuleDefinition ModuleDefinition { get; set; }

TypeSystem typeSystem;

// Init logging delegates to make testing easier
public ModuleWeaver()
{
Expand All @@ -22,17 +20,21 @@ public ModuleWeaver()

public void Execute()
{
typeSystem = ModuleDefinition.TypeSystem;

foreach (var type in ModuleDefinition.Types.Where(CanHaveWith))
{
RemoveWith(type);
AddWith(type);
LogInfo($"Added method 'With' to type '{type.Name}'.");
}
}

private bool CanHaveWith(TypeDefinition type)
{
if (!type.GetMethods().Any(m => m.Name.StartsWith("With")))
{
return false;
}

var ctors = type.GetConstructors().ToArray();
if (ctors.Length != 1)
{
Expand All @@ -46,7 +48,15 @@ private bool CanHaveWith(TypeDefinition type)
return false;
}

return parameters.All(par => type.Properties.Any(pro => ToPropertyName(par.Name) == pro.Name));
return parameters.All(par => type.Properties.Any(pro => string.Compare(par.Name, pro.Name, StringComparison.InvariantCultureIgnoreCase) == 0));
}

private void RemoveWith(TypeDefinition type)
{
foreach (var method in type.GetMethods().Where(m => m.Name.StartsWith("With")).ToArray())
{
type.Methods.Remove(method);
}
}

private void AddWith(TypeDefinition type)
Expand All @@ -55,10 +65,10 @@ private void AddWith(TypeDefinition type)
foreach (var property in ctor.Parameters)
{
var parameterName = property.Name;
string propertyName = ToPropertyName(property.Name);
var getter = type.GetMethods().Where(m => m.Name == $"get_{propertyName}").First();
var getter = type.GetMethods().First(m => string.Compare(m.Name, $"get_{property.Name}", StringComparison.InvariantCultureIgnoreCase) == 0);

var methodName = ctor.Parameters.Except(new[] { property }).Any(p => p.ParameterType == property.ParameterType)
string propertyName = ToPropertyName(property.Name);
var methodName = ctor.Parameters.Except(new[] { property }).Any(p => p.ParameterType.FullName == property.ParameterType.FullName)
? $"With{propertyName}" : "With";
var method = new MethodDefinition(methodName, MethodAttributes.Public, type);
method.Parameters.Add(new ParameterDefinition(parameterName, ParameterAttributes.None, getter.ReturnType));
Expand All @@ -72,7 +82,7 @@ private void AddWith(TypeDefinition type)
}
else
{
var getterParameter = type.GetMethods().Where(m => m.Name == $"get_{ToPropertyName(parameter.Name)}").First();
var getterParameter = type.GetMethods().First(m => string.Compare(m.Name, $"get_{parameter.Name}", StringComparison.InvariantCultureIgnoreCase) == 0);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Call, getterParameter);
}
Expand Down

0 comments on commit 6d14964

Please sign in to comment.