Skip to content

Commit f1e1f6b

Browse files
author
Sébastien Geiser
committed
Pfiou it's Cecil is hard
1 parent 08d6a79 commit f1e1f6b

File tree

6 files changed

+189
-13
lines changed

6 files changed

+189
-13
lines changed

CodingSeb.Localization.FodyAddin.Fody/FodyExtensions.cs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,34 @@
1-
using Mono.Cecil;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
24
using System.Linq;
5+
using Fody;
6+
using Mono.Cecil;
7+
using Mono.Cecil.Cil;
8+
using Mono.Collections.Generic;
39

410
namespace CodingSeb.Localization.FodyAddin.Fody
511
{
612
public static class FodyExtensions
713
{
14+
private static readonly List<string> propertyChangedTriggerMethodCommonNames = new List<string>()
15+
{
16+
"NotifyPropertyChanged",
17+
"NotifyPropertyChange",
18+
"OnPropertyChanged",
19+
"OnPropertyChange",
20+
"RaisePropertyChanged",
21+
"RaisePropertyChange",
22+
"NotifyOfPropertyChange",
23+
"NotifyOfPropertyChanged",
24+
"TriggerPropertyChange",
25+
"TriggerPropertyChanged",
26+
"PropertyChangeTrigger",
27+
"PropertyChangedTrigger",
28+
"FirePropertyChange",
29+
"FirePropertyChanged",
30+
};
31+
832
public static bool IsINotifyPropertyChanged(this TypeDefinition typeDefinition)
933
{
1034
if (typeDefinition?.FullName.Equals("System.Object") != false)
@@ -16,5 +40,22 @@ public static bool IsINotifyPropertyChanged(this TypeDefinition typeDefinition)
1640
else
1741
return false;
1842
}
43+
44+
public static MethodDefinition FindPropertyChangedTriggerMethod(this TypeDefinition typeDefinition)
45+
{
46+
if (typeDefinition?.FullName.Equals("System.Object") != false)
47+
return null;
48+
else if (typeDefinition.Methods.SingleOrDefault(m => propertyChangedTriggerMethodCommonNames.Any(name => name.Equals(m.Name, StringComparison.OrdinalIgnoreCase))) is MethodDefinition method)
49+
return method;
50+
else if (typeDefinition.BaseType is TypeDefinition parentTypeDefinition)
51+
return parentTypeDefinition.FindPropertyChangedTriggerMethod();
52+
else
53+
return null;
54+
}
55+
56+
public static bool HasLocalizeAttribute(this PropertyDefinition propertyDefinition)
57+
{
58+
return propertyDefinition.CustomAttributes.Any(attribute => attribute.AttributeType.Name.Equals("LocalizeAttribute"));
59+
}
1960
}
2061
}

CodingSeb.Localization.FodyAddin.Fody/ModuleWeaver.cs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Fody;
66
using Mono.Cecil;
77
using Mono.Cecil.Cil;
8+
using Mono.Cecil.Rocks;
89
using Mono.Collections.Generic;
910

1011
namespace CodingSeb.Localization.FodyAddin.Fody
@@ -13,35 +14,85 @@ public class ModuleWeaver : BaseModuleWeaver
1314
{
1415
public MethodReference DebuggerNonUserCodeAttributeConstructor;
1516

17+
private const MethodAttributes staticConstructorAttributes =
18+
MethodAttributes.Private |
19+
MethodAttributes.HideBySig |
20+
MethodAttributes.Static |
21+
MethodAttributes.SpecialName |
22+
MethodAttributes.RTSpecialName;
23+
1624
public override void Execute()
1725
{
1826
ModuleDefinition
1927
.Types
2028
.Where(typeDefinition =>
2129
typeDefinition.IsClass &&
2230
typeDefinition.IsINotifyPropertyChanged() &&
23-
typeDefinition.Properties.Any(property => property.CustomAttributes.Any(attribute => attribute.AttributeType.Name.Equals("LocalizeAttribute"))))
31+
typeDefinition.Properties.Any(property => property.HasLocalizeAttribute()))
2432
.ToList()
25-
.ForEach(typeDefinition =>
26-
{
27-
AddHelloWorld(typeDefinition);
28-
});
33+
.ForEach(AddCurrentLanguageChangedSensitivity);
2934
}
3035

31-
void AddHelloWorld(TypeDefinition typeDefinition)
36+
private void AddCurrentLanguageChangedSensitivity(TypeDefinition typeDefinition)
3237
{
33-
var method = new MethodDefinition("World", MethodAttributes.Public, TypeSystem.StringReference);
38+
IEnumerable<PropertyDefinition> propertyToLocalize = typeDefinition.Properties.Where(property => property.HasLocalizeAttribute());
39+
40+
FieldDefinition propertyListFieldDefinition = AddLocalizedPropertyNamesStaticList(typeDefinition, propertyToLocalize.Select(p => p.Name));
41+
42+
var method = new MethodDefinition("__CurrentLanguageChanged__", MethodAttributes.Private, TypeSystem.VoidReference);
3443
ILProcessor processor = method.Body.GetILProcessor();
35-
var instructions = method.Body.Instructions;
3644
processor.Append(Instruction.Create(OpCodes.Ldstr, "Hello World"));
3745
processor.Append(Instruction.Create(OpCodes.Ret));
3846
typeDefinition.Methods.Add(method);
3947
}
4048

49+
public FieldDefinition AddLocalizedPropertyNamesStaticList(TypeDefinition typeDefinition, IEnumerable<string> propertiesNames)
50+
{
51+
TypeReference listOfStringTypeReference = ModuleDefinition.ImportReference(typeof(List<string>));
52+
FieldDefinition field = new FieldDefinition("__localizedPropertyNames__", FieldAttributes.Private | FieldAttributes.Static, listOfStringTypeReference);
53+
54+
var constructorOnStringList = ModuleDefinition.ImportReference(typeof(List<string>).GetConstructor(new Type[] { }));
55+
var addMethodOnStringList = ModuleDefinition.ImportReference(typeof(List<string>).GetMethod("Add", new Type[] { typeof(string) }));
56+
57+
typeDefinition.Fields.Add(field);
58+
59+
MethodDefinition staticConstructor = GetOrCreateStaticConstructor(typeDefinition);
60+
61+
var il = staticConstructor.Body.GetILProcessor();
62+
63+
if (il.Body.Instructions.LastOrDefault(i => i.OpCode == OpCodes.Ret) is Instruction instruction)
64+
{
65+
il.Remove(instruction);
66+
}
67+
68+
il.Emit(OpCodes.Newobj, constructorOnStringList);
69+
70+
//propertiesNames.ToList().ForEach(propertyName =>
71+
//{
72+
// instructions.Add(Instruction.Create(OpCodes.Dup));
73+
// instructions.Add(Instruction.Create(OpCodes.Ldstr, propertyName));
74+
// instructions.Add(Instruction.Create(OpCodes.Callvirt, addMethodOnStringList));
75+
//});
76+
77+
il.Emit(OpCodes.Stsfld, field);
78+
79+
il.Emit(OpCodes.Ret);
80+
81+
return field;
82+
}
83+
84+
public MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
85+
{
86+
return typeDefinition.GetStaticConstructor() ??
87+
new MethodDefinition(".cctor", staticConstructorAttributes, TypeSystem.VoidReference);
88+
}
89+
4190
public override IEnumerable<string> GetAssembliesForScanning()
4291
{
4392
yield return "netstandard";
4493
yield return "mscorlib";
94+
yield return "WindowsBase";
95+
yield return "CodingSeb.Localization";
4596
}
4697

4798
public override bool ShouldCleanReference => true;
Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
using CodingSeb.Localization.FodyAddin.Fody;
22
using Fody;
33
using System;
4+
using System.Collections.Generic;
5+
using System.ComponentModel;
6+
using System.Reflection;
7+
using System.Runtime.CompilerServices;
48
using Xunit;
59

610
namespace CodingSeb.Localization.FodyAddin.Tests
711
{
812
public class WeaverTests
913
{
10-
static TestResult testResult;
14+
private static readonly TestResult testResult;
1115

1216
static WeaverTests()
1317
{
@@ -16,12 +20,35 @@ static WeaverTests()
1620
}
1721

1822
[Fact]
19-
public void ValidateThatPropertyWithLocalizeAttributeIsUpdatewhenLanguageChanged()
23+
public void ValidateThatPropertyWithLocalizeAttributeIsUpdateWhenLanguageChanged()
2024
{
2125
var type = testResult.Assembly.GetType("CodingSeb.Localization.AssemblyToProcess.LocalizedWithFodyClass");
26+
27+
FieldInfo propertyNamesField = type.GetField("__localizedPropertyNames__", BindingFlags.NonPublic | BindingFlags.Static);
28+
29+
Assert.NotNull(propertyNamesField);
30+
2231
var instance = (dynamic)Activator.CreateInstance(type);
2332

24-
Assert.Equal("Hello World", instance.World());
33+
List<string> listOfPropertyNames = propertyNamesField.GetValue(instance) as List<string>;
34+
35+
Assert.NotNull(listOfPropertyNames);
36+
Assert.Contains("TestPropertya", listOfPropertyNames);
37+
38+
INotifyPropertyChanged notifyPropertyChanged = instance as INotifyPropertyChanged;
39+
40+
string propertyName = string.Empty;
41+
42+
void NotifyPropertyChanged_PropertyChanged(object sender, PropertyChangedEventArgs e)
43+
{
44+
propertyName = e.PropertyName;
45+
}
46+
47+
notifyPropertyChanged.PropertyChanged += NotifyPropertyChanged_PropertyChanged;
48+
49+
notifyPropertyChanged.PropertyChanged -= NotifyPropertyChanged_PropertyChanged;
50+
51+
//Assert.Equal("TestProperty" ,propertyName);
2552
}
2653
}
2754
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\CodingSeb.Localization\CodingSeb.Localization.csproj" />
9+
</ItemGroup>
10+
11+
<ItemGroup>
12+
<Reference Include="WindowsBase">
13+
<HintPath>..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.7\WindowsBase.dll</HintPath>
14+
</Reference>
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.ComponentModel;
4+
using System.Runtime.CompilerServices;
5+
using System.Windows;
6+
7+
namespace CodingSeb.Localization.FodyAddin.WantedResult
8+
{
9+
public class DirectResult : INotifyPropertyChanged
10+
{
11+
private static readonly List<string> __localizedPropertyNames__ = new List<string>()
12+
{
13+
"TestProperty",
14+
"OtherProperty"
15+
};
16+
17+
public DirectResult()
18+
{
19+
WeakEventManager<Loc, CurrentLanguageChangedEventArgs>.AddHandler(Loc.Instance, nameof(Loc.Instance.CurrentLanguageChanged), __CurrentLanguageChanged__);
20+
}
21+
22+
protected void __CurrentLanguageChanged__(object sender, CurrentLanguageChangedEventArgs e)
23+
{
24+
__localizedPropertyNames__.ForEach(__NotifyPropertyChanged__);
25+
}
26+
27+
private void __NotifyPropertyChanged__(string propertyName)
28+
{
29+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
30+
}
31+
32+
public event PropertyChangedEventHandler PropertyChanged;
33+
}
34+
}

CodingSeb.Localization.sln

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodingSeb.Localization.Fody
2525
EndProject
2626
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodingSeb.Localization.AssemblyToProcess", "CodingSeb.Localization.AssemblyToProcess\CodingSeb.Localization.AssemblyToProcess.csproj", "{05900915-BD84-495D-B6FB-E0C8F662BE0F}"
2727
EndProject
28-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingSeb.Localization.FodyAddin.Tests", "CodingSeb.Localization.FodyAddin.Tests\CodingSeb.Localization.FodyAddin.Tests.csproj", "{20437578-F0F8-4106-87D1-34CD65E69195}"
28+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodingSeb.Localization.FodyAddin.Tests", "CodingSeb.Localization.FodyAddin.Tests\CodingSeb.Localization.FodyAddin.Tests.csproj", "{20437578-F0F8-4106-87D1-34CD65E69195}"
29+
EndProject
30+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodingSeb.Localization.FodyAddin.WantedResult", "CodingSeb.Localization.FodyAddin.WantedResult\CodingSeb.Localization.FodyAddin.WantedResult.csproj", "{0953B24E-86C7-4DEC-886D-10EA81559693}"
2931
EndProject
3032
Global
3133
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -81,6 +83,10 @@ Global
8183
{20437578-F0F8-4106-87D1-34CD65E69195}.Debug|Any CPU.Build.0 = Debug|Any CPU
8284
{20437578-F0F8-4106-87D1-34CD65E69195}.Release|Any CPU.ActiveCfg = Release|Any CPU
8385
{20437578-F0F8-4106-87D1-34CD65E69195}.Release|Any CPU.Build.0 = Release|Any CPU
86+
{0953B24E-86C7-4DEC-886D-10EA81559693}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
87+
{0953B24E-86C7-4DEC-886D-10EA81559693}.Debug|Any CPU.Build.0 = Debug|Any CPU
88+
{0953B24E-86C7-4DEC-886D-10EA81559693}.Release|Any CPU.ActiveCfg = Release|Any CPU
89+
{0953B24E-86C7-4DEC-886D-10EA81559693}.Release|Any CPU.Build.0 = Release|Any CPU
8490
EndGlobalSection
8591
GlobalSection(SolutionProperties) = preSolution
8692
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)