-
Notifications
You must be signed in to change notification settings - Fork 57
/
Copy pathValidateResourceStringAttribute.cs
147 lines (127 loc) · 5.32 KB
/
ValidateResourceStringAttribute.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
using PostSharp.Constraints;
using PostSharp.Extensibility;
using PostSharp.Reflection;
using PostSharp.Reflection.MethodBody;
using System;
using System.Reflection;
using System.Resources;
namespace PostSharp.Samples.ValidateResourceString
{
/// <summary>
/// Custom attribute that, when applied to a <see cref="string" /> parameter, writes a warning at build-time that the
/// value passed to the parameter is a valid name for a string stored in
/// a managed resource.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
[MulticastAttributeUsage(MulticastTargets.Parameter)]
public sealed class ValidateResourceStringAttribute : ReferentialConstraint
{
private readonly string resourceBaseName;
/// <summary>
/// Initializes a new <see cref="ValidateResourceStringAttribute" />.
/// </summary>
/// <param name="resourceBaseName">Name of the managed resource containing the string.</param>
public ValidateResourceStringAttribute(string resourceBaseName)
{
this.resourceBaseName = resourceBaseName;
}
/// <summary>
/// Validates that the attribute has been applied to a valid parameter.
/// </summary>
/// <param name="target">The parameter to which the attribute has been applied.</param>
/// <returns><c>true</c> if the attribute is applied to a valid parameter, otherwise <c>false</c>.</returns>
public override bool ValidateConstraint(object target)
{
// Validate that the attribute has been applied to a parameter of type string.
var parameter = (ParameterInfo) target;
if (parameter.ParameterType != typeof(string))
{
Message.Write(parameter, SeverityType.Error, "VRN01",
"Cannot use [ValidateResourceString] on parameter {0} because it is not of type string.", parameter);
return false;
}
if (!(parameter.Member is MethodBase))
{
Message.Write(parameter, SeverityType.Error, "VRN02",
"Cannot use [ValidateResourceString] on parameter {0} because the attribute can only be applied to method parameters.",
parameter);
return false;
}
// Validate that the current assembly contains a resource of the given name.
var assembly = GetType().Assembly;
try
{
new ResourceManager(resourceBaseName, assembly);
}
catch (Exception e)
{
Message.Write(parameter, SeverityType.Error, "VRN03",
"Cannot load a managed resource named \"{1}\" from assembly \"{0}\": {2}", assembly.GetName().Name,
resourceBaseName, e.Message);
return false;
}
return true;
}
/// <summary>
/// Validates an <see cref="Assembly" /> against the current constraint.
/// </summary>
/// <param name="target">Parameter to which the current constraint has been applied.</param>
/// <param name="assembly">Assembly being validated.</param>
public override void ValidateCode(object target, Assembly assembly)
{
var parameter = (ParameterInfo) target;
var resourceManager = new ResourceManager(resourceBaseName, assembly);
// Get the list of methods referencing the parent method of the parameter.
var reflectionService =
PostSharpEnvironment.CurrentProject.GetService<IMethodBodyService>();
var usages = ReflectionSearch.GetMethodsUsingDeclaration(parameter.Member);
foreach (var usage in usages)
{
// Decompiles the method into expression trees.
var methodBody = reflectionService.GetMethodBody(usage.UsingMethod,
MethodBodyAbstractionLevel.ExpressionTree);
// Visit the method body.
var visitor = new Visitor(parameter, resourceManager);
visitor.VisitMethodBody(methodBody);
}
}
/// <summary>
/// Visits all expressions in the method body.
/// </summary>
private class Visitor : MethodBodyVisitor
{
private readonly ParameterInfo parameter;
private readonly ResourceManager resourceManager;
public Visitor(ParameterInfo parameter, ResourceManager resourceManager)
{
this.resourceManager = resourceManager;
this.parameter = parameter;
}
/// <summary>
/// Visits a method call.
/// </summary>
/// <param name="expression"></param>
/// <returns></returns>
public override object VisitMethodCallExpression(IMethodCallExpression expression)
{
if (expression.Method == parameter.Member)
{
// We are inspecting the call to the method of interest.
var argument = expression.Arguments[parameter.Position];
var constantExpression = argument as IConstantExpression;
if (constantExpression != null)
{
var resourceName = (string) constantExpression.Value;
if (resourceManager.GetString(resourceName) == null)
{
Message.Write(expression.ParentMethodBody.Method, SeverityType.Warning, "VRN04",
"The string \"{0}\" in method {1} is not a valid resource name.", resourceName,
expression.Method);
}
}
}
return base.VisitMethodCallExpression(expression);
}
}
}
}