Skip to content

Commit 27ee29a

Browse files
committed
Consolidate Identifier validations
1 parent 236492e commit 27ee29a

File tree

9 files changed

+286
-171
lines changed

9 files changed

+286
-171
lines changed

Rubberduck.CodeAnalysis/Inspections/Concrete/UseMeaningfulNameInspection.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Rubberduck.Parsing.Inspections.Abstract;
1010
using Rubberduck.Parsing.Symbols;
1111
using Rubberduck.Parsing.VBA;
12+
using Rubberduck.Refactorings.Common;
1213
using Rubberduck.Resources;
1314
using Rubberduck.SettingsProvider;
1415
using static Rubberduck.Parsing.Grammar.VBAParser;
@@ -67,8 +68,8 @@ protected override IEnumerable<IInspectionResult> DoGetInspectionResults()
6768
(declaration.ParentDeclaration == null ||
6869
!IgnoreDeclarationTypes.Contains(declaration.ParentDeclaration.DeclarationType) &&
6970
!handlers.Contains(declaration.ParentDeclaration)) &&
70-
!whitelistedNames.Contains(declaration.IdentifierName) &&
71-
!VariableNameValidator.IsMeaningfulName(declaration.IdentifierName));
71+
!whitelistedNames.Contains(declaration.IdentifierName) &&
72+
!VBAIdentifierValidator.IsMeaningfulIdentifier(declaration.IdentifierName));
7273

7374
return (from issue in issues select CreateInspectionResult(this, issue))
7475
.ToList();

Rubberduck.CodeAnalysis/Inspections/Helper/VariableNameValidator.cs

Lines changed: 0 additions & 80 deletions
This file was deleted.

Rubberduck.CodeAnalysis/QuickFixes/AssignedByValParameterMakeLocalCopyQuickFix.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Rubberduck.Parsing.Rewriter;
1414
using Rubberduck.Parsing.VBA;
1515
using System.Diagnostics;
16+
using Rubberduck.Refactorings.Common;
1617

1718
namespace Rubberduck.Inspections.QuickFixes
1819
{
@@ -75,7 +76,7 @@ private string PromptForLocalVariableName(Declaration target)
7576
}
7677
}
7778

78-
private bool IsNameCollision(string newName)
79+
private bool IsNameCollision(string newName)
7980
=> _declarationFinderProvider.DeclarationFinder.FindNewDeclarationNameConflicts(newName, _quickFixTarget).Any();
8081

8182
private string GetDefaultLocalIdentifier(Declaration target)
@@ -99,7 +100,7 @@ private string GetDefaultLocalIdentifier(Declaration target)
99100

100101
private bool IsValidVariableName(string variableName)
101102
{
102-
return VariableNameValidator.IsValidName(variableName)
103+
return VBAIdentifierValidator.IsValidIdentifier(variableName, DeclarationType.Variable)
103104
&& !IsNameCollision(variableName);
104105
}
105106

Rubberduck.Core/UI/Refactorings/AssignedByValParameterQuickFixDialog.cs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
22
using System.Windows.Forms;
33
using Rubberduck.Common;
4+
using Rubberduck.Parsing.Symbols;
5+
using Rubberduck.Refactorings.Common;
46
using Rubberduck.Resources;
57

68
namespace Rubberduck.UI.Refactorings
@@ -52,32 +54,28 @@ private string GetVariableNameFeedback()
5254
{
5355
return string.Empty;
5456
}
57+
5558
if (_isConflictingName(NewName))
5659
{
5760
return string.Format(RubberduckUI.AssignedByValDialog_NewNameAlreadyUsedFormat, NewName);
5861
}
59-
if (VariableNameValidator.StartsWithDigit(NewName))
60-
{
61-
return RubberduckUI.AssignedByValDialog_DoesNotStartWithLetter;
62-
}
63-
if (VariableNameValidator.HasSpecialCharacters(NewName))
64-
{
65-
return RubberduckUI.AssignedByValDialog_InvalidCharacters;
66-
}
67-
if (VariableNameValidator.IsReservedIdentifier(NewName))
62+
63+
if (VBAIdentifierValidator.TryMatchInvalidIdentifierCriteria(NewName, DeclarationType.Variable, out var invalidMessage))
6864
{
69-
return string.Format(RubberduckUI.AssignedByValDialog_ReservedKeywordFormat, NewName);
65+
return invalidMessage;
7066
}
71-
if (!VariableNameValidator.IsMeaningfulName(NewName))
67+
68+
if (VBAIdentifierValidator.TryMatchMeaninglessIdentifierCriteria(NewName, out var meaninglessNameMessage))
7269
{
73-
return string.Format(RubberduckUI.AssignedByValDialog_QuestionableEntryFormat, NewName);
70+
return string.Format(RubberduckUI.AssignedByValDialog_MeaninglessNameFormat, meaninglessNameMessage);
7471
}
72+
7573
return string.Empty;
7674
}
7775

7876
private void SetControlsProperties()
7977
{
80-
var isValid = VariableNameValidator.IsValidName(NewName);
78+
var isValid = VBAIdentifierValidator.IsValidIdentifier(NewName, DeclarationType.Variable);
8179
OkButton.Visible = isValid;
8280
OkButton.Enabled = isValid;
8381
InvalidNameValidationIcon.Visible = !isValid;

Rubberduck.Core/UI/Refactorings/Rename/RenameViewModel.cs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
using Rubberduck.Parsing.VBA;
99
using Rubberduck.Refactorings.Rename;
1010
using Rubberduck.Resources;
11+
using Rubberduck.Common;
12+
using Rubberduck.Refactorings.Common;
1113

1214
namespace Rubberduck.UI.Refactorings.Rename
1315
{
@@ -57,25 +59,21 @@ public bool IsValidName
5759
{
5860
get
5961
{
60-
if (Target == null)
62+
if (Target == null) { return false; }
63+
64+
if (VBAIdentifierValidator.IsValidIdentifier(NewName, Target.DeclarationType))
6165
{
62-
return false;
66+
return !NewName.Equals(Target.IdentifierName, StringComparison.InvariantCultureIgnoreCase);
6367
}
6468

65-
var tokenValues = typeof(Tokens).GetFields().Select(item => item.GetValue(null)).Cast<string>().Select(item => item);
66-
67-
return !(NewName.Equals(Target.IdentifierName, StringComparison.InvariantCultureIgnoreCase)) &&
68-
char.IsLetter(NewName.FirstOrDefault()) &&
69-
!tokenValues.Contains(NewName, StringComparer.InvariantCultureIgnoreCase) &&
70-
!NewName.Any(c => !char.IsLetterOrDigit(c) && c != '_') &&
71-
NewName.Length <= (Target.DeclarationType.HasFlag(DeclarationType.Module) ? Declaration.MaxModuleNameLength : Declaration.MaxMemberNameLength);
69+
return false;
7270
}
7371
}
7472

7573
protected override void DialogOk()
7674
{
7775
if (Target == null
78-
|| (DeclarationsWithConflictingName(Model.NewName, Model.Target).Any()
76+
|| (_declarationFinderProvider.DeclarationFinder.FindNewDeclarationNameConflicts(NewName, Model.Target).Any()
7977
&& !UserConfirmsToProceedWithConflictingName(Model.NewName, Model.Target)))
8078
{
8179
base.DialogCancel();
@@ -86,11 +84,6 @@ protected override void DialogOk()
8684
}
8785
}
8886

89-
private IEnumerable<Declaration> DeclarationsWithConflictingName(string newName, Declaration target)
90-
{
91-
return _declarationFinderProvider.DeclarationFinder.FindNewDeclarationNameConflicts(newName, target);
92-
}
93-
9487
private bool UserConfirmsToProceedWithConflictingName(string newName, Declaration target)
9588
{
9689
var message = string.Format(RubberduckUI.RenameDialog_ConflictingNames, newName, target.IdentifierName);
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
using Rubberduck.Parsing.Grammar;
2+
using Rubberduck.Parsing.Symbols;
3+
using Rubberduck.Resources;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Globalization;
7+
using System.Linq;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace Rubberduck.Refactorings.Common
12+
{
13+
public static class VBAIdentifierValidator
14+
{
15+
private static IEnumerable<string> ReservedIdentifiers =
16+
typeof(Tokens).GetFields().Select(item => item.GetValue(null).ToString()).ToArray();
17+
18+
/// <summary>
19+
/// Predicate function determining if an identifier string's content will trigger a result for the UseMeaningfulNames inspection.
20+
/// </summary>
21+
public static bool IsMeaningfulIdentifier(string name)
22+
=> !TryMatchMeaninglessIdentifierCriteria(name, out _);
23+
24+
/// <summary>
25+
/// Evaluates if an identifier string's content will trigger a result for the UseMeaningfulNames inspection.
26+
/// </summary>
27+
/// <returns>Message indicating that the string will result in a UseMeaningfulNames inspection result</returns>
28+
public static bool TryMatchMeaninglessIdentifierCriteria(string name, out string criteriaMatchMessage)
29+
{
30+
criteriaMatchMessage = string.Empty;
31+
string Vowels = "aeiouyàâäéèêëïîöôùûü";
32+
int MinimumNameLength = 3;
33+
34+
bool HasVowel()
35+
=> name.Any(character => Vowels.Any(vowel =>
36+
string.Compare(vowel.ToString(CultureInfo.InvariantCulture),
37+
character.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase) == 0));
38+
39+
bool HasConsonant()
40+
=> !name.All(character => Vowels.Any(vowel =>
41+
string.Compare(vowel.ToString(CultureInfo.InvariantCulture),
42+
character.ToString(CultureInfo.InvariantCulture), StringComparison.OrdinalIgnoreCase) == 0));
43+
44+
bool IsRepeatedCharacter()
45+
{
46+
var firstLetter = name.First().ToString(CultureInfo.InvariantCulture);
47+
return name.All(a => string.Compare(a.ToString(CultureInfo.InvariantCulture), firstLetter,
48+
StringComparison.OrdinalIgnoreCase) == 0);
49+
}
50+
51+
bool EndsWithDigit()
52+
=> char.IsDigit(name.Last());
53+
54+
bool IsTooShort()
55+
=> name.Length < MinimumNameLength;
56+
57+
var isMeaningless = !(HasVowel()
58+
&& HasConsonant()
59+
&& !IsRepeatedCharacter()
60+
&& !IsTooShort()
61+
&& !EndsWithDigit());
62+
63+
if (isMeaningless)
64+
{
65+
criteriaMatchMessage = string.Format(RubberduckUI.MeaninglessNameCriteriaMatchFormat, name);
66+
}
67+
return isMeaningless;
68+
}
69+
70+
/// <summary>
71+
/// Predicate function determining if an identifier string conforms to MS-VBAL naming requirements
72+
/// </summary>
73+
public static bool IsValidIdentifier(string name, DeclarationType declarationType)
74+
=> !TryMatchInvalidIdentifierCriteria(name, declarationType, out _);
75+
76+
/// <summary>
77+
/// Evaluates an identifier string's conformance with MS-VBAL naming requirements.
78+
/// </summary>
79+
/// <returns>Message for first matching invalid identifier criteria. Or, an empty string if the identifier is valid</returns>
80+
public static bool TryMatchInvalidIdentifierCriteria(string name, DeclarationType declarationType, out string criteriaMatchMessage)
81+
{
82+
criteriaMatchMessage = string.Empty;
83+
84+
var maxNameLength = declarationType.HasFlag(DeclarationType.Module)
85+
? Declaration.MaxModuleNameLength : Declaration.MaxMemberNameLength;
86+
87+
if (string.IsNullOrEmpty(name))
88+
{
89+
criteriaMatchMessage = RubberduckUI.InvalidNameCriteria_IsNullOrEmpty;
90+
return true;
91+
}
92+
93+
//Does not start with a letter
94+
if (!char.IsLetter(name.First()))
95+
{
96+
criteriaMatchMessage = string.Format(RubberduckUI.InvalidNameCriteria_DoesNotStartWithLetterFormat, name);
97+
return true;
98+
}
99+
100+
//Has special characters
101+
if (name.Any(c => !char.IsLetterOrDigit(c) && c != '_'))
102+
{
103+
criteriaMatchMessage = string.Format(RubberduckUI.InvalidNameCriteria_InvalidCharactersFormat, name);
104+
return true;
105+
}
106+
107+
//Is a reserved identifier
108+
if (ReservedIdentifiers.Contains(name, StringComparer.InvariantCultureIgnoreCase))
109+
{
110+
criteriaMatchMessage = string.Format(RubberduckUI.InvalidNameCriteria_IsReservedKeywordFormat, name);
111+
return true;
112+
}
113+
114+
//"VBA" identifier not allowed for projects
115+
if (declarationType.HasFlag(DeclarationType.Project)
116+
&& name.Equals("VBA", StringComparison.InvariantCultureIgnoreCase))
117+
{
118+
criteriaMatchMessage = string.Format(RubberduckUI.InvalidNameCriteria_IsReservedKeywordFormat, name);
119+
return true;
120+
}
121+
122+
//Exceeds max length
123+
if (name.Length > maxNameLength)
124+
{
125+
criteriaMatchMessage = string.Format(RubberduckUI.InvalidNameCriteria_ExceedsMaximumLengthFormat, name);
126+
return true;
127+
}
128+
return false;
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)