Skip to content

Commit 4dc9bab

Browse files
authored
Merge pull request #5612 from BZngr/EF_ConflictFinderMeQualifier
EncapsulateFieldRefactoring - Fix false-positive conflict detections involving 'Me' qualifier
2 parents 9e4c73d + 1e9cf7b commit 4dc9bab

14 files changed

+254
-149
lines changed

Rubberduck.Refactorings/EncapsulateField/ConflictDetection/EncapsulateFieldConflictFinder.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,14 @@ private bool HasConflictWithLocalDeclarationIdentifiers(IEncapsulateFieldCandida
276276
.Select(f => f.Declaration));
277277
}
278278

279+
bool IsQualifedReference(IdentifierReference identifierReference)
280+
=> identifierReference.Context.Parent is VBAParser.MemberAccessExprContext
281+
|| identifierReference.Context.Parent is VBAParser.WithMemberAccessExprContext;
282+
279283
//Only check IdentifierReferences in the declaring module because encapsulated field
280284
//references in other modules will be module-qualified.
281-
var candidateLocalReferences = candidate.Declaration.References.Where(rf => rf.QualifiedModuleName == candidate.QualifiedModuleName);
285+
var candidateLocalReferences = candidate.Declaration.References.Where(rf => rf.QualifiedModuleName == candidate.QualifiedModuleName
286+
&& !IsQualifedReference(rf));
282287

283288
var localDeclarationConflictCandidates = membersToEvaluate.Where(localDec => candidateLocalReferences
284289
.Any(cr => cr.ParentScoping == localDec.ParentScopeDeclaration));

RubberduckTests/Refactoring/EncapsulateField/EncapsulateFieldCodeBuilderTests.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@ namespace RubberduckTests.Refactoring.EncapsulateField
99
[TestFixture]
1010
public class EncapsulateFieldCodeBuilderTests
1111
{
12-
private EncapsulateFieldTestSupport Support { get; } = new EncapsulateFieldTestSupport();
13-
14-
[SetUp]
15-
public void ExecutesBeforeAllTests()
16-
{
17-
Support.ResetResolver();
18-
}
19-
2012
[Test]
2113
[Category("Refactorings")]
2214
[Category("Encapsulate Field")]
@@ -103,8 +95,9 @@ public void BuildPropertyBlock_Set(string asTypeName)
10395
var encapsulateTarget = state.AllUserDeclarations.Single(d => d.IdentifierName.Equals(prototypeIdentifier));
10496

10597
attrSet.Declaration = encapsulateTarget;
98+
var resolver = EncapsulateFieldTestSupport.GetResolver(state);
10699

107-
return Support.Resolve<IEncapsulateFieldCodeBuilder>(state)
100+
return resolver.Resolve<IEncapsulateFieldCodeBuilder>()
108101
.BuildPropertyBlocks(attrSet);
109102
}
110103
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
using NUnit.Framework;
2+
using Rubberduck.Parsing.Symbols;
3+
using Rubberduck.Refactorings;
4+
using Rubberduck.Refactorings.EncapsulateField;
5+
using Rubberduck.VBEditor.SafeComWrappers;
6+
using RubberduckTests.Mocks;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
10+
namespace RubberduckTests.Refactoring.EncapsulateField
11+
{
12+
[TestFixture]
13+
public class EncapsulateFieldConflictFinderTests
14+
{
15+
[Test]
16+
[Category("Refactorings")]
17+
[Category("Encapsulate Field")]
18+
[Category(nameof(EncapsulateFieldConflictFinder))]
19+
public void RespectsMemberAccessExpressionUsingMe()
20+
{
21+
var targetField = "Fizz";
22+
23+
var inputCode =
24+
$@"Public {targetField} As Integer
25+
26+
Public Sub NoConflict(Fizz As Integer)
27+
Me.Fizz = Fizz
28+
End Sub";
29+
30+
var result = TestIsConflictingIdentifier("Fizz", inputCode, targetField);
31+
Assert.IsFalse(result);
32+
}
33+
34+
[Test]
35+
[Category("Refactorings")]
36+
[Category("Encapsulate Field")]
37+
[Category(nameof(EncapsulateFieldConflictFinder))]
38+
public void RespectsWithMemberAccessExpressionUsingMe()
39+
{
40+
var targetField = "Fizz";
41+
42+
var inputCode =
43+
$@"Public Fizz As Integer
44+
45+
Public Sub NoConflict(Fizz As Integer)
46+
With Me
47+
.Fizz = Fizz
48+
End With
49+
End Sub";
50+
var result = TestIsConflictingIdentifier("Fizz", inputCode, targetField);
51+
Assert.IsFalse(result);
52+
}
53+
54+
[Test]
55+
[Category("Refactorings")]
56+
[Category("Encapsulate Field")]
57+
[Category(nameof(EncapsulateFieldConflictFinder))]
58+
public void RespectsWithMemberAccessExpressionUsingMe_Parameter()
59+
{
60+
var targetField = "Fizz";
61+
var inputCode =
62+
$@"Public Fizz As Integer
63+
64+
Public Sub CopyFromAnotherInstance(Fizz As MockVbeBuilder.TestModuleName)
65+
Me.Fizz = Fizz.Fizz
66+
End Sub";
67+
68+
var result = TestIsConflictingIdentifier("Fizz", inputCode, targetField);
69+
Assert.IsFalse(result);
70+
}
71+
72+
[TestCase("Dim thisConflicts As Long", null, true)]
73+
[TestCase("Dim thisConflicts As Long", "Me.", false)]
74+
[TestCase("Const thisConflicts As Long = 10", null, true)]
75+
[TestCase("Const thisConflicts As Long = 10", "Me.", false)]
76+
[Category("Refactorings")]
77+
[Category("Encapsulate Field")]
78+
[Category(nameof(EncapsulateFieldConflictFinder))]
79+
public void LocalDeclarationsRespectsQualifier(string declaration, string qualifier, bool expected)
80+
{
81+
var targetField = "Fizz";
82+
var inputCode =
83+
$@"Public Fizz As Integer
84+
85+
Public Sub DoSomething()
86+
{declaration}
87+
{qualifier}Fizz = thisConflicts + 1
88+
End Sub";
89+
90+
var result = TestIsConflictingIdentifier("thisConflicts", inputCode, targetField);
91+
Assert.AreEqual(expected, result);
92+
}
93+
94+
[TestCase("thisConFlicts", true)]
95+
[TestCase("DoSomething", true)]
96+
[TestCase("TestString", true)]
97+
[TestCase("TestConst", true)]
98+
[TestCase("FirstValue", false)]
99+
[TestCase("LocalConst", false)]
100+
[TestCase("LocalVariable", false)]
101+
[Category("Refactorings")]
102+
[Category("Encapsulate Field")]
103+
[Category(nameof(EncapsulateFieldConflictFinder))]
104+
public void DetectsModuleEntityConflicts(string newName, bool expected)
105+
{
106+
var targetField = "Fizz";
107+
var inputCode =
108+
$@"
109+
Public Fizz As Integer
110+
Private TestString As String
111+
112+
Private Const TestConst As Long = 10
113+
114+
Private Enum TestEnum
115+
ThisConflicts
116+
EnumTwo
117+
End Enum
118+
119+
Private Type TestType
120+
FirstValue As Long
121+
End Type
122+
123+
Private Sub DoSomething()
124+
Const localConst As String = ""Test""
125+
Dim localVariable As Long
126+
End Sub
127+
";
128+
129+
var result = TestIsConflictingIdentifier(newName, inputCode, targetField);
130+
Assert.AreEqual(expected, result);
131+
}
132+
133+
private bool TestIsConflictingIdentifier(string testIdentifier, string inputCode, string targetFieldName)
134+
{
135+
var vbe = MockVbeBuilder.BuildFromModules((MockVbeBuilder.TestModuleName, inputCode, ComponentType.ClassModule));
136+
137+
var state = MockParser.CreateAndParse(vbe.Object);
138+
using (state)
139+
{
140+
141+
var field = state.DeclarationFinder.UserDeclarations(DeclarationType.Variable)
142+
.Single(d => d.IdentifierName == targetFieldName);
143+
144+
var fieldModel = new FieldEncapsulationModel(field as VariableDeclaration);
145+
146+
var modelFactory = EncapsulateFieldTestSupport.GetResolver(state).Resolve<IEncapsulateFieldUseBackingFieldModelFactory>();
147+
var model = modelFactory.Create(new List<FieldEncapsulationModel>() { fieldModel });
148+
149+
var efCandidate = model.EncapsulationCandidates.First(c => c.Declaration == field);
150+
efCandidate.EncapsulateFlag = true;
151+
152+
return model.ConflictFinder.IsConflictingIdentifier(efCandidate, testIdentifier, out _);
153+
}
154+
}
155+
}
156+
}

RubberduckTests/Refactoring/EncapsulateField/EncapsulateFieldTestSupport.cs

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,21 @@ namespace RubberduckTests.Refactoring.EncapsulateField
1717
{
1818
public class EncapsulateFieldTestSupport : EncapsulateFieldInteractiveRefactoringTest
1919
{
20-
private EncapsulateFieldTestsResolver _testResolver;
21-
22-
public void ResetResolver()
23-
{
24-
_testResolver = null;
25-
}
26-
27-
public T Resolve<T>(IDeclarationFinderProvider declarationFinderProvider, IRewritingManager rewritingManager = null, ISelectionService selectionService = null) where T : class
20+
public EncapsulateFieldTestsResolver SetupResolver(IDeclarationFinderProvider declarationFinderProvider, IRewritingManager rewritingManager = null, ISelectionService selectionService = null)
2821
{
29-
SetupResolver(declarationFinderProvider, rewritingManager, selectionService);
30-
return Resolve<T>() as T;
22+
return GetResolver(declarationFinderProvider, rewritingManager, selectionService);
3123
}
3224

33-
public T Resolve<T>() where T : class
34-
=> _testResolver?.Resolve<T>() as T ?? throw new InvalidOperationException("Test Resolver not initialized. Call 'SetupResolver(...)' or use one of the 'Resolve<T>()' overloads");
35-
36-
public void SetupResolver(IDeclarationFinderProvider declarationFinderProvider, IRewritingManager rewritingManager = null, ISelectionService selectionService = null)
25+
public static EncapsulateFieldTestsResolver GetResolver(IDeclarationFinderProvider declarationFinderProvider, IRewritingManager rewritingManager = null, ISelectionService selectionService = null)
3726
{
3827
if (declarationFinderProvider is null)
3928
{
4029
throw new ArgumentNullException("declarationFinderProvider is null");
4130
}
4231

43-
if (_testResolver is null)
44-
{
45-
_testResolver = new EncapsulateFieldTestsResolver(declarationFinderProvider, rewritingManager, selectionService);
46-
_testResolver.Install(new WindsorContainer(), null);
47-
}
32+
var resolver = new EncapsulateFieldTestsResolver(declarationFinderProvider, rewritingManager, selectionService);
33+
resolver.Install(new WindsorContainer(), null);
34+
return resolver;
4835
}
4936

5037
public string RHSIdentifier => Rubberduck.Resources.Refactorings.Refactorings.CodeBuilder_DefaultPropertyRHSParam;
@@ -148,13 +135,13 @@ public IRefactoring SupportTestRefactoring(
148135
RefactoringUserInteraction<IEncapsulateFieldPresenter, EncapsulateFieldModel> userInteraction,
149136
ISelectionService selectionService)
150137
{
151-
SetupResolver(state, rewritingManager, selectionService);
152-
return new EncapsulateFieldRefactoring(Resolve<EncapsulateFieldRefactoringAction>(state, rewritingManager, selectionService),
153-
Resolve<EncapsulateFieldPreviewProvider>(),
154-
Resolve<IEncapsulateFieldModelFactory>(),
138+
var resolver = SetupResolver(state, rewritingManager, selectionService);
139+
return new EncapsulateFieldRefactoring(resolver.Resolve<EncapsulateFieldRefactoringAction>(),
140+
resolver.Resolve<EncapsulateFieldPreviewProvider>(),
141+
resolver.Resolve<IEncapsulateFieldModelFactory>(),
155142
userInteraction,
156143
selectionService,
157-
Resolve<ISelectedDeclarationProvider>());
144+
resolver.Resolve<ISelectedDeclarationProvider>());
158145
}
159146

160147
public IDictionary<string, string> RefactoredCode(
@@ -202,9 +189,11 @@ public IEncapsulateFieldCandidate RetrieveEncapsulateFieldCandidate(IVBE vbe, st
202189
var state = MockParser.CreateAndParse(vbe);
203190
using (state)
204191
{
192+
var resolver = SetupResolver(state);
193+
205194
var match = state.DeclarationFinder.MatchName(fieldName).Where(m => m.DeclarationType.Equals(declarationType)).Single();
206195

207-
var model = Resolve<IEncapsulateFieldModelFactory>(state).Create(match);
196+
var model = resolver.Resolve<IEncapsulateFieldModelFactory>().Create(match);
208197

209198
model.ConflictFinder.AssignNoConflictIdentifiers(model[match.IdentifierName]);
210199

@@ -232,6 +221,32 @@ protected override IRefactoring TestRefactoring(
232221
{
233222
return SupportTestRefactoring(rewritingManager, state, userInteraction, selectionService);
234223
}
224+
225+
public string RefactoredCode<TRefactoring,TModel>(string code, Func<RubberduckParserState, EncapsulateFieldTestsResolver, TModel> modelBuilder) where TRefactoring : CodeOnlyRefactoringActionBase<TModel> where TModel : class, IRefactoringModel
226+
{
227+
var vbe = MockVbeBuilder.BuildFromSingleStandardModule(code, out _).Object;
228+
var componentName = vbe.SelectedVBComponent.Name;
229+
var refactored = RefactoredCode<TRefactoring,TModel>(vbe, modelBuilder);
230+
return refactored[componentName];
231+
}
232+
233+
public IDictionary<string, string> RefactoredCode<TRefactoring,TModel>(IVBE vbe, Func<RubberduckParserState, EncapsulateFieldTestsResolver, TModel> modelBuilder) where TRefactoring: CodeOnlyRefactoringActionBase<TModel> where TModel: class, IRefactoringModel
234+
{
235+
var (state, rewritingManager) = MockParser.CreateAndParseWithRewritingManager(vbe);
236+
using (state)
237+
{
238+
var resolver = GetResolver(state, rewritingManager);
239+
240+
var refactoring = resolver.Resolve<TRefactoring>();
241+
242+
var model = modelBuilder(state, resolver);
243+
244+
refactoring.Refactor(model);
245+
246+
return vbe.ActiveVBProject.VBComponents
247+
.ToDictionary(component => component.Name, component => component.CodeModule.Content());
248+
}
249+
}
235250
}
236251

237252
public class TestEncapsulationAttributes

RubberduckTests/Refactoring/EncapsulateField/EncapsulateFieldUseBackingField/EncapsulateArrayFieldTests.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,6 @@ public class EncapsulateArrayFieldTests : EncapsulateFieldInteractiveRefactoring
1717
{
1818
private EncapsulateFieldTestSupport Support { get; } = new EncapsulateFieldTestSupport();
1919

20-
[SetUp]
21-
public void ExecutesBeforeAllTests()
22-
{
23-
Support.ResetResolver();
24-
}
25-
2620
[TestCase("Private", "mArray(5) As String", "mArray(5) As String")]
2721
[TestCase("Public", "mArray(5) As String", "mArray(5) As String")]
2822
[TestCase("Private", "mArray(5,2,3) As String", "mArray(5,2,3) As String")]

RubberduckTests/Refactoring/EncapsulateField/EncapsulateFieldUseBackingField/EncapsulateFieldTests.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,6 @@ public class EncapsulateFieldTests : EncapsulateFieldInteractiveRefactoringTest
2121
{
2222
private EncapsulateFieldTestSupport Support { get; } = new EncapsulateFieldTestSupport();
2323

24-
[SetUp]
25-
public void ExecutesBeforeAllTests()
26-
{
27-
Support.ResetResolver();
28-
}
29-
3024
[TestCase("fizz", true, "baz", true, "buzz", true)]
3125
[TestCase("fizz", false, "baz", true, "buzz", true)]
3226
[TestCase("fizz", false, "baz", false, "buzz", true)]

0 commit comments

Comments
 (0)