Skip to content

Commit f3226ef

Browse files
committed
Simplify ConflictDetection
Refactored ConflictFinders to a single class and interface. Restructured EncapsulateFieldRefactoring object instantiations and initialization given that all now share a single IEncapsulateFieldConflictFinder instance.
1 parent 84b7a68 commit f3226ef

20 files changed

+520
-583
lines changed
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
using Rubberduck.Parsing.Symbols;
2+
using Rubberduck.Parsing.VBA;
3+
using Rubberduck.Refactorings.Common;
4+
using Rubberduck.Refactorings.EncapsulateField.Extensions;
5+
using Rubberduck.Resources;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
10+
namespace Rubberduck.Refactorings.EncapsulateField
11+
{
12+
public interface IEncapsulateFieldConflictFinder
13+
{
14+
bool IsConflictingIdentifier(IEncapsulateFieldCandidate field, string identifierToCompare, out string errorMessage);
15+
(bool IsValid, string ValidationError) ValidateEncapsulationAttributes(IEncapsulateFieldCandidate field);
16+
void AssignNoConflictIdentifiers(IEncapsulateFieldCandidate candidate);
17+
void AssignNoConflictIdentifiers(IObjectStateUDT stateUDT);
18+
void AssignNoConflictIdentifiers(IEnumerable<IEncapsulateFieldCandidate> candidates);
19+
}
20+
21+
public class EncapsulateFieldConflictFinder : IEncapsulateFieldConflictFinder
22+
{
23+
private static List<DeclarationType> _declarationTypesThatNeverConflictWithFieldAndPropertyIdentifiers = new List<DeclarationType>()
24+
{
25+
DeclarationType.Project,
26+
DeclarationType.ProceduralModule,
27+
DeclarationType.ClassModule,
28+
DeclarationType.Parameter,
29+
DeclarationType.Enumeration,
30+
DeclarationType.UserDefinedType,
31+
DeclarationType.UserDefinedTypeMember
32+
};
33+
34+
private readonly IDeclarationFinderProvider _declarationFinderProvider;
35+
private readonly List<IEncapsulateFieldCandidate> _fieldCandidates;
36+
private readonly List<IUserDefinedTypeMemberCandidate> _udtMemberCandidates;
37+
private readonly List<IEncapsulateFieldCandidate> _allCandidates;
38+
private readonly List<IObjectStateUDT> _objectStateUDTs;
39+
private readonly List<Declaration> _members;
40+
private readonly List<Declaration> _membersThatCanConflictWithFieldAndPropertyIdentifiers;
41+
private readonly List<Declaration> _existingUserUDTsAndEnums;
42+
43+
public EncapsulateFieldConflictFinder(IDeclarationFinderProvider declarationFinderProvider, IEnumerable<IEncapsulateFieldCandidate> candidates, IEnumerable<IObjectStateUDT> objectStateUDTs)
44+
{
45+
_declarationFinderProvider = declarationFinderProvider;
46+
47+
_fieldCandidates = candidates.ToList();
48+
49+
_udtMemberCandidates = new List<IUserDefinedTypeMemberCandidate>();
50+
_fieldCandidates.ForEach(c => LoadUDTMembers(c));
51+
52+
_allCandidates = _fieldCandidates.Concat(_udtMemberCandidates).ToList();
53+
54+
_objectStateUDTs = objectStateUDTs.ToList();
55+
56+
_members = _declarationFinderProvider.DeclarationFinder.Members(_allCandidates.First().QualifiedModuleName).ToList();
57+
58+
_membersThatCanConflictWithFieldAndPropertyIdentifiers =
59+
_members.Where(m => !_declarationTypesThatNeverConflictWithFieldAndPropertyIdentifiers.Contains(m.DeclarationType)).ToList();
60+
61+
_existingUserUDTsAndEnums = _members.Where(m => m.DeclarationType.HasFlag(DeclarationType.UserDefinedType)
62+
|| m.DeclarationType.HasFlag(DeclarationType.Enumeration)).ToList();
63+
}
64+
65+
public (bool IsValid, string ValidationError) ValidateEncapsulationAttributes(IEncapsulateFieldCandidate field)
66+
{
67+
if (!field.EncapsulateFlag)
68+
{
69+
return (true, string.Empty);
70+
}
71+
72+
var declarationType = field is IEncapsulateFieldAsUDTMemberCandidate
73+
? DeclarationType.UserDefinedTypeMember
74+
: field.Declaration.DeclarationType;
75+
76+
var errorMessage = string.Empty;
77+
78+
var hasInvalidIdentifierOrHasConflicts =
79+
VBAIdentifierValidator.TryMatchInvalidIdentifierCriteria(field.PropertyIdentifier, declarationType, out errorMessage, field.Declaration.IsArray)
80+
|| IsConflictingIdentifier(field, field.PropertyIdentifier, out errorMessage)
81+
|| IsConflictingIdentifier(field, field.BackingIdentifier, out errorMessage)
82+
|| field is IEncapsulateFieldAsUDTMemberCandidate && ConflictsWithExistingUDTMembers(SelectedObjectStateUDT(), field.BackingIdentifier, out errorMessage);
83+
84+
return (string.IsNullOrEmpty(errorMessage), errorMessage);
85+
}
86+
87+
public bool IsConflictingIdentifier(IEncapsulateFieldCandidate field, string identifierToCompare, out string errorMessage)
88+
{
89+
errorMessage = string.Empty;
90+
if (HasConflictIdentifiers(field, identifierToCompare))
91+
{
92+
errorMessage = RubberduckUI.EncapsulateField_NameConflictDetected;
93+
}
94+
return !string.IsNullOrEmpty(errorMessage);
95+
}
96+
97+
public void AssignNoConflictIdentifiers(IEnumerable<IEncapsulateFieldCandidate> candidates)
98+
{
99+
foreach (var candidate in candidates.Where(c => c.EncapsulateFlag))
100+
{
101+
ResolveFieldConflicts(candidate);
102+
}
103+
}
104+
105+
private void ResolveFieldConflicts(IEncapsulateFieldCandidate candidate)
106+
{
107+
AssignNoConflictIdentifiers(candidate);
108+
if (candidate is IUserDefinedTypeCandidate udtCandidate)
109+
{
110+
ResolveUDTMemberConflicts(udtCandidate.Members);
111+
}
112+
}
113+
114+
private void ResolveUDTMemberConflicts(IEnumerable<IUserDefinedTypeMemberCandidate> members)
115+
{
116+
foreach (var member in members)
117+
{
118+
AssignNoConflictIdentifiers(member);
119+
if (member.WrappedCandidate is IUserDefinedTypeCandidate childUDT
120+
&& childUDT.Declaration.AsTypeDeclaration.HasPrivateAccessibility())
121+
{
122+
ResolveFieldConflicts(childUDT);
123+
}
124+
}
125+
}
126+
127+
public void AssignNoConflictIdentifiers(IEncapsulateFieldCandidate candidate)
128+
{
129+
if (candidate is IEncapsulateFieldAsUDTMemberCandidate)
130+
{
131+
AssignIdentifier(
132+
() => ConflictsWithExistingUDTMembers(SelectedObjectStateUDT(), candidate.PropertyIdentifier, out _),
133+
() => IncrementPropertyIdentifier(candidate));
134+
return;
135+
}
136+
137+
AssignNoConflictPropertyIdentifier(candidate);
138+
AssignNoConflictBackingFieldIdentifier(candidate);
139+
}
140+
141+
public void AssignNoConflictIdentifiers(IObjectStateUDT stateUDT)
142+
{
143+
AssignIdentifier(
144+
() => HasConflictingFieldIdentifier(stateUDT, stateUDT.FieldIdentifier),
145+
() => stateUDT.FieldIdentifier = stateUDT.FieldIdentifier.IncrementEncapsulationIdentifier());
146+
147+
AssignIdentifier(
148+
() => _existingUserUDTsAndEnums.Any(m => m.IdentifierName.IsEquivalentVBAIdentifierTo(stateUDT.TypeIdentifier)),
149+
() => stateUDT.TypeIdentifier = stateUDT.TypeIdentifier.IncrementEncapsulationIdentifier());
150+
}
151+
152+
private IObjectStateUDT SelectedObjectStateUDT() => _objectStateUDTs.SingleOrDefault(os => os.IsSelected);
153+
154+
private static bool ConflictsWithExistingUDTMembers(IObjectStateUDT objectStateUDT, string identifier, out string errorMessage)
155+
{
156+
errorMessage = string.Empty;
157+
if (objectStateUDT?.ExistingMembers.Any(nm => nm.IdentifierName.IsEquivalentVBAIdentifierTo(identifier)) ?? false)
158+
{
159+
errorMessage = RubberduckUI.EncapsulateField_NameConflictDetected;
160+
}
161+
return !string.IsNullOrEmpty(errorMessage);
162+
}
163+
164+
private void IncrementPropertyIdentifier(IEncapsulateFieldCandidate candidate)
165+
=> candidate.PropertyIdentifier = candidate.PropertyIdentifier.IncrementEncapsulationIdentifier();
166+
167+
private void AssignNoConflictPropertyIdentifier(IEncapsulateFieldCandidate candidate)
168+
{
169+
AssignIdentifier(
170+
() => IsConflictingIdentifier(candidate, candidate.PropertyIdentifier, out _),
171+
() => IncrementPropertyIdentifier(candidate));
172+
}
173+
174+
private void AssignNoConflictBackingFieldIdentifier(IEncapsulateFieldCandidate candidate)
175+
{
176+
//Private UserDefinedTypes are never used directly as a backing field - so never change their identifier.
177+
//The backing fields for an encapsulated Private UDT are its members.
178+
if (!(candidate is UserDefinedTypeMemberCandidate
179+
|| candidate is IUserDefinedTypeCandidate udtCandidate && udtCandidate.TypeDeclarationIsPrivate))
180+
{
181+
AssignIdentifier(
182+
() => IsConflictingIdentifier(candidate, candidate.BackingIdentifier, out _),
183+
() => candidate.BackingIdentifier = candidate.BackingIdentifier.IncrementEncapsulationIdentifier());
184+
}
185+
}
186+
187+
private static void AssignIdentifier(Func<bool> hasConflict, Action incrementIdentifier, int maxAttempts = 20)
188+
{
189+
var guard = 0;
190+
while (guard++ < maxAttempts && hasConflict())
191+
{
192+
incrementIdentifier();
193+
}
194+
195+
if (guard >= maxAttempts)
196+
{
197+
throw new OverflowException("Unable to assign a non conflicting identifier");
198+
}
199+
}
200+
201+
private bool HasConflictIdentifiers(IEncapsulateFieldCandidate candidate, string identifierToCompare)
202+
{
203+
if (_allCandidates.Where(c => c.TargetID != candidate.TargetID
204+
&& c.EncapsulateFlag
205+
&& c.PropertyIdentifier.IsEquivalentVBAIdentifierTo(identifierToCompare)).Any())
206+
{
207+
return true;
208+
}
209+
210+
var membersToEvaluate = _members.Where(d => d != candidate.Declaration);
211+
212+
if (candidate is IEncapsulateFieldAsUDTMemberCandidate)
213+
{
214+
membersToEvaluate = membersToEvaluate.Except(
215+
_fieldCandidates.Where(fc => fc.EncapsulateFlag && fc.Declaration.DeclarationType.HasFlag(DeclarationType.Variable))
216+
.Select(f => f.Declaration));
217+
}
218+
219+
var nameConflictCandidates = membersToEvaluate.Where(member => !(member.IsLocalVariable() || member.IsLocalConstant()
220+
|| _declarationTypesThatNeverConflictWithFieldAndPropertyIdentifiers.Contains(member.DeclarationType)));
221+
222+
if (nameConflictCandidates.Any(m => m.IdentifierName.IsEquivalentVBAIdentifierTo(identifierToCompare)))
223+
{
224+
return true;
225+
}
226+
227+
//Only check IdentifierReferences in the declaring module because IdentifierReferences in
228+
//other modules will be module-qualified.
229+
var candidateLocalReferences = candidate.Declaration.References.Where(rf => rf.QualifiedModuleName == candidate.QualifiedModuleName);
230+
231+
var localDeclarationConflictCandidates = membersToEvaluate.Where(localDec => candidateLocalReferences
232+
.Any(cr => cr.ParentScoping == localDec.ParentScopeDeclaration));
233+
234+
return localDeclarationConflictCandidates.Any(m => m.IdentifierName.IsEquivalentVBAIdentifierTo(identifierToCompare));
235+
}
236+
237+
private bool HasConflictingFieldIdentifier(IObjectStateUDT candidate, string identifierToCompare)
238+
{
239+
if (candidate.IsExistingDeclaration)
240+
{
241+
return false;
242+
}
243+
244+
if (_allCandidates.Where(c => c.EncapsulateFlag
245+
&& c.PropertyIdentifier.IsEquivalentVBAIdentifierTo(identifierToCompare)).Any())
246+
{
247+
return true;
248+
}
249+
250+
var fieldsToRemoveFromConflictCandidates = _fieldCandidates
251+
.Where(fc => fc.EncapsulateFlag && fc.Declaration.DeclarationType.HasFlag(DeclarationType.Variable))
252+
.Select(fc => fc.Declaration);
253+
254+
var nameConflictCandidates =
255+
_members.Except(fieldsToRemoveFromConflictCandidates)
256+
.Where(member => !(member.IsLocalVariable() || member.IsLocalConstant()
257+
|| _declarationTypesThatNeverConflictWithFieldAndPropertyIdentifiers.Contains(member.DeclarationType)));
258+
259+
return nameConflictCandidates.Any(m => m.IdentifierName.IsEquivalentVBAIdentifierTo(identifierToCompare));
260+
}
261+
262+
private void LoadUDTMembers(IEncapsulateFieldCandidate candidate)
263+
{
264+
if (!(candidate is IUserDefinedTypeCandidate udtCandidate))
265+
{
266+
return;
267+
}
268+
269+
foreach (var member in udtCandidate.Members)
270+
{
271+
_udtMemberCandidates.Add(member);
272+
273+
if (member.WrappedCandidate is IUserDefinedTypeCandidate childUDT
274+
&& childUDT.Declaration.AsTypeDeclaration.HasPrivateAccessibility())
275+
{
276+
//recursive till a non-UserDefinedType member is found
277+
LoadUDTMembers(childUDT);
278+
}
279+
}
280+
}
281+
}
282+
}

0 commit comments

Comments
 (0)