Skip to content

Commit f9140b5

Browse files
authored
Merge pull request #5189 from BZngr/ConsolidateNameValidation
Consolidates identifier name validation logic
2 parents cdfb716 + 2c2a6de commit f9140b5

File tree

9 files changed

+279
-171
lines changed

9 files changed

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

0 commit comments

Comments
 (0)