5
// Michael Hutchinson <mhutch@xamarin.com>
6
// Mike KrĆ¼ger <mkrueger@xamarin.com>
8
// Copyright (c) 2012 Xamarin <http://xamarin.com>
10
// Permission is hereby granted, free of charge, to any person obtaining a copy
11
// of this software and associated documentation files (the "Software"), to deal
12
// in the Software without restriction, including without limitation the rights
13
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
// copies of the Software, and to permit persons to whom the Software is
15
// furnished to do so, subject to the following conditions:
17
// The above copyright notice and this permission notice shall be included in
18
// all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
30
using System.Collections.Generic;
32
namespace ICSharpCode.NRefactory.CSharp.Refactoring
34
public class NamingRule : IEquatable<NamingRule>
36
public string Name { get; set; }
38
/// If set, identifiers are required to be prefixed with one of these values.
40
public string[] RequiredPrefixes { get; set; }
43
/// If set, identifiers are allowed to be prefixed with one of these values.
45
public string[] AllowedPrefixes { get; set; }
48
/// If set, identifiers are required to be suffixed with one of these values.
50
public string[] RequiredSuffixes { get; set; }
53
/// If set, identifiers cannot be prefixed by any of these values.
55
public string[] ForbiddenPrefixes { get; set; }
58
/// If set, identifiers cannot be suffixed by with any of these values.
60
public string[] ForbiddenSuffixes { get; set; }
63
/// Gets or sets the affected entity.
65
public AffectedEntity AffectedEntity { get; set; }
68
/// Gets or sets the modifiers mask
70
public Modifiers VisibilityMask { get; set; }
73
/// The way that the identifier is cased and that words are separated.
75
public NamingStyle NamingStyle { get; set; }
77
public bool IncludeStaticEntities { get; set; }
78
public bool IncludeInstanceMembers { get; set; }
80
public bool IsValid(string name)
83
bool foundPrefix = false;
84
if (RequiredPrefixes != null && RequiredPrefixes.Length > 0) {
85
var prefix = RequiredPrefixes.FirstOrDefault(p => id.StartsWith(p, StringComparison.Ordinal));
89
id = id.Substring(prefix.Length);
93
if (!foundPrefix && AllowedPrefixes != null && AllowedPrefixes.Length > 0) {
94
var prefix = AllowedPrefixes.FirstOrDefault(p => id.StartsWith(p, StringComparison.Ordinal));
96
id = id.Substring(prefix.Length);
101
if (!foundPrefix && ForbiddenPrefixes != null && ForbiddenPrefixes.Length > 0) {
102
if (ForbiddenPrefixes.Any(p => id.StartsWith(p, StringComparison.Ordinal))) {
107
if (RequiredSuffixes != null && RequiredSuffixes.Length > 0) {
108
var suffix = RequiredSuffixes.FirstOrDefault(s => id.EndsWith(s, StringComparison.Ordinal));
109
if (suffix == null) {
112
id = id.Substring(0, id.Length - suffix.Length);
113
} else if (ForbiddenSuffixes != null && ForbiddenSuffixes.Length > 0) {
114
if (ForbiddenSuffixes.Any(p => id.EndsWith(p, StringComparison.Ordinal))) {
119
switch (NamingStyle) {
120
case NamingStyle.AllLower:
121
return !id.Any(ch => char.IsLetter(ch) && char.IsUpper(ch));
122
case NamingStyle.AllUpper:
123
return !id.Any(ch => char.IsLetter(ch) && char.IsLower(ch));
124
case NamingStyle.CamelCase:
125
return id.Length == 0 || (char.IsLower(id [0]) && NoUnderscore(id));
126
case NamingStyle.PascalCase:
127
return id.Length == 0 || (char.IsUpper(id [0]) && NoUnderscore(id));
128
case NamingStyle.FirstUpper:
129
return id.Length == 0 && char.IsUpper(id [0]) && !id.Skip(1).Any(ch => char.IsLetter(ch) && char.IsUpper(ch));
134
public NamingRule(AffectedEntity affectedEntity)
136
AffectedEntity = affectedEntity;
137
VisibilityMask = Modifiers.VisibilityMask;
138
IncludeStaticEntities = true;
139
IncludeInstanceMembers = true;
142
static bool NoUnderscore(string id)
144
return id.IndexOf('_') < 0;
147
// static bool NoUnderscoreWithoutNumber(string id)
149
// int idx = id.IndexOf('_');
150
// while (idx >= 0 && idx < id.Length) {
151
// if ((idx + 2 >= id.Length || !char.IsDigit(id [idx + 1])) && (idx == 0 || !char.IsDigit(id [idx - 1]))) {
154
// idx = id.IndexOf('_', idx + 1);
160
public string GetErrorMessage(BaseRefactoringContext ctx, string name, out IList<string> suggestedNames)
162
suggestedNames = new List<string>();
165
string errorMessage = null;
167
bool missingRequiredPrefix = false;
168
bool missingRequiredSuffix = false;
169
string requiredPrefix = null;
170
string allowedPrefix = null;
171
string suffix = null;
173
if (AllowedPrefixes != null && AllowedPrefixes.Length > 0) {
174
allowedPrefix = AllowedPrefixes.FirstOrDefault(p => id.StartsWith(p, StringComparison.Ordinal));
175
if (allowedPrefix != null)
176
id = id.Substring(allowedPrefix.Length);
180
if (RequiredPrefixes != null && RequiredPrefixes.Length > 0) {
181
requiredPrefix = RequiredPrefixes.FirstOrDefault(p => id.StartsWith(p, StringComparison.Ordinal));
182
if (requiredPrefix == null) {
183
errorMessage = string.Format(ctx.TranslateString("Name should have prefix '{0}'."), RequiredPrefixes [0]);
184
missingRequiredPrefix = true;
186
id = id.Substring(requiredPrefix.Length);
188
} else if (ForbiddenPrefixes != null && ForbiddenPrefixes.Length > 0) {
189
requiredPrefix = ForbiddenPrefixes.FirstOrDefault(p => id.StartsWith(p, StringComparison.Ordinal));
190
if (requiredPrefix != null) {
191
errorMessage = string.Format(ctx.TranslateString("Name has forbidden prefix '{0}'."), requiredPrefix);
192
id = id.Substring(requiredPrefix.Length);
196
if (RequiredSuffixes != null && RequiredSuffixes.Length > 0) {
197
suffix = RequiredSuffixes.FirstOrDefault(s => id.EndsWith(s, StringComparison.Ordinal));
198
if (suffix == null) {
199
errorMessage = string.Format(ctx.TranslateString("Name should have suffix '{0}'."), RequiredSuffixes [0]);
200
missingRequiredSuffix = true;
202
id = id.Substring(0, id.Length - suffix.Length);
204
} else if (ForbiddenSuffixes != null && ForbiddenSuffixes.Length > 0) {
205
suffix = ForbiddenSuffixes.FirstOrDefault(p => id.EndsWith(p, StringComparison.Ordinal));
206
if (suffix != null) {
207
errorMessage = string.Format(ctx.TranslateString("Name has forbidden suffix '{0}'."), suffix);
208
id = id.Substring(0, id.Length - suffix.Length);
212
switch (NamingStyle) {
213
case NamingStyle.AllLower:
214
if (id.Any(ch => char.IsLetter(ch) && char.IsUpper(ch))) {
215
errorMessage = string.Format(ctx.TranslateString("'{0}' contains upper case letters."), name);
216
suggestedNames.Add(LowerCaseIdentifier(WordParser.BreakWords(id)));
218
suggestedNames.Add(id);
221
case NamingStyle.AllUpper:
222
if (id.Any(ch => char.IsLetter(ch) && char.IsLower(ch))) {
223
errorMessage = string.Format(ctx.TranslateString("'{0}' contains lower case letters."), name);
224
suggestedNames.Add(UpperCaseIdentifier(WordParser.BreakWords(id)));
226
suggestedNames.Add(id);
229
case NamingStyle.CamelCase:
230
if (id.Length > 0 && char.IsUpper(id [0])) {
231
errorMessage = string.Format(ctx.TranslateString("'{0}' should start with a lower case letter."), name);
232
} else if (!NoUnderscore(id)) {
233
errorMessage = string.Format(ctx.TranslateString("'{0}' should not separate words with an underscore."), name);
235
suggestedNames.Add(id);
238
suggestedNames.Add(CamelCaseIdentifier(WordParser.BreakWords(id)));
240
case NamingStyle.PascalCase:
241
if (id.Length > 0 && char.IsLower(id [0])) {
242
errorMessage = string.Format(ctx.TranslateString("'{0}' should start with an upper case letter."), name);
243
} else if (!NoUnderscore(id)) {
244
errorMessage = string.Format(ctx.TranslateString("'{0}' should not separate words with an underscore."), name);
246
suggestedNames.Add(id);
249
suggestedNames.Add(PascalCaseIdentifier(WordParser.BreakWords(id)));
251
case NamingStyle.FirstUpper:
252
if (id.Length > 0 && char.IsLower(id [0])) {
253
errorMessage = string.Format(ctx.TranslateString("'{0}' should start with an upper case letter."), name);
254
} else if (id.Take(1).Any(ch => char.IsLetter(ch) && char.IsUpper(ch))) {
255
errorMessage = string.Format(ctx.TranslateString("'{0}' contains an upper case letter after the first."), name);
257
suggestedNames.Add(id);
260
suggestedNames.Add(FirstUpperIdentifier(WordParser.BreakWords(id)));
264
if (requiredPrefix != null) {
265
for (int i = 0; i < suggestedNames.Count; i++) {
266
suggestedNames [i] = requiredPrefix + suggestedNames [i];
268
} else if (allowedPrefix != null) {
269
int count = suggestedNames.Count;
270
for (int i = 0; i < count; i++) {
271
suggestedNames.Add(suggestedNames [i]);
272
suggestedNames [i] = allowedPrefix + suggestedNames [i];
274
} else if (missingRequiredPrefix) {
275
for (int i = 0; i < suggestedNames.Count; i++) {
276
var n = suggestedNames [i];
278
foreach (var p in RequiredPrefixes) {
281
suggestedNames [i] = p + n;
283
suggestedNames.Add(p + n);
289
if (suffix != null) {
290
for (int i = 0; i < suggestedNames.Count; i++) {
291
suggestedNames [i] = suggestedNames [i] + suffix;
293
} else if (missingRequiredSuffix) {
294
for (int i = 0; i < suggestedNames.Count; i++) {
295
var n = suggestedNames [i];
297
foreach (var s in RequiredSuffixes) {
300
suggestedNames [i] = n + s;
302
suggestedNames.Add(n + s);
309
// should never happen.
310
?? "no known errors.";
313
static string CamelCaseIdentifier (List<string> words)
315
var sb = new StringBuilder ();
316
sb.Append (words[0].ToLower ());
317
for (int i = 1; i < words.Count; i++) {
318
// if (sb.Length > 0 && (char.IsDigit (sb[sb.Length-1]) || char.IsDigit (words[i][0])))
320
AppendCapitalized (words[i], sb);
322
return sb.ToString ();
325
static string PascalCaseIdentifier (List<string> words)
327
var sb = new StringBuilder ();
328
for (int i = 0; i < words.Count; i++) {
329
// if (sb.Length > 0 && (char.IsDigit (sb[sb.Length-1]) || char.IsDigit (words[i][0])))
331
AppendCapitalized (words[i], sb);
333
return sb.ToString ();
336
static string LowerCaseIdentifier (List<string> words)
338
var sb = new StringBuilder ();
339
sb.Append (words[0].ToLower ());
340
for (int i = 1; i < words.Count; i++) {
342
sb.Append (words[i].ToLower ());
344
return sb.ToString ();
347
static string UpperCaseIdentifier (List<string> words)
349
var sb = new StringBuilder ();
350
sb.Append (words[0].ToUpper ());
351
for (int i = 1; i < words.Count; i++) {
353
sb.Append (words[i].ToUpper ());
355
return sb.ToString ();
358
static string FirstUpperIdentifier (List<string> words)
360
var sb = new StringBuilder ();
361
AppendCapitalized (words[0], sb);
362
for (int i = 1; i < words.Count; i++) {
364
sb.Append (words[i].ToLower ());
366
return sb.ToString ();
369
static void AppendCapitalized(string word, StringBuilder sb)
371
sb.Append(word.ToLower());
372
sb [sb.Length - word.Length] = char.ToUpper(sb [sb.Length - word.Length]);
375
#region IEquatable implementation
376
public bool Equals (NamingRule other)
378
return Name == other.Name &&
379
AffectedEntity == other.AffectedEntity &&
380
VisibilityMask == other.VisibilityMask &&
381
NamingStyle == other.NamingStyle;
385
public NamingRule Clone()
387
return (NamingRule)MemberwiseClone();
390
public override string ToString()
392
return string.Format("[NamingRule: Name={0}, AffectedEntity={1}, VisibilityMask={2}, NamingStyle={3}, IncludeStaticEntities={4}, IncludeInstanceMembers={5}]", Name, AffectedEntity, VisibilityMask, NamingStyle, IncludeStaticEntities, IncludeInstanceMembers);