Skip to content

Commit 115f2e9

Browse files
committed
Split RenameRefactoringAction into a part that requires suspension and one that does not
This allows to reuse the part not requiring suspension inside other refactoring actions and in refactoring preview providers. There is some code duplication between the two parts, but I chose not to split them until a third version arises.
1 parent 166ddf6 commit 115f2e9

File tree

4 files changed

+411
-289
lines changed

4 files changed

+411
-289
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using Rubberduck.Parsing.Grammar;
6+
using Rubberduck.Parsing.Rewriter;
7+
using Rubberduck.Parsing.Symbols;
8+
using Rubberduck.Parsing.VBA;
9+
using Rubberduck.VBEditor.ComManagement;
10+
11+
namespace Rubberduck.Refactorings.Rename
12+
{
13+
public class RenameCodeDefinedIdentifierRefactoringAction : CodeOnlyRefactoringActionBase<RenameModel>
14+
{
15+
private const string AppendUnderscoreFormat = "{0}_";
16+
private const string PrependUnderscoreFormat = "_{0}";
17+
18+
private readonly IDeclarationFinderProvider _declarationFinderProvider;
19+
private readonly IProjectsProvider _projectsProvider;
20+
private readonly IDictionary<DeclarationType, Action<RenameModel, IRewriteSession>> _renameActions;
21+
22+
public RenameCodeDefinedIdentifierRefactoringAction(
23+
IDeclarationFinderProvider declarationFinderProvider,
24+
IProjectsProvider projectsProvider,
25+
IRewritingManager rewritingManager)
26+
: base(rewritingManager)
27+
{
28+
_declarationFinderProvider = declarationFinderProvider;
29+
_projectsProvider = projectsProvider;
30+
31+
_renameActions = new Dictionary<DeclarationType, Action<RenameModel, IRewriteSession>>
32+
{
33+
{DeclarationType.Member, RenameMember},
34+
{DeclarationType.Parameter, RenameParameter},
35+
{DeclarationType.Event, RenameEvent},
36+
{DeclarationType.Variable, RenameVariable}
37+
};
38+
}
39+
40+
public override void Refactor(RenameModel model, IRewriteSession rewriteSession)
41+
{
42+
var actionKeys = _renameActions.Keys.Where(decType => model.Target.DeclarationType.HasFlag(decType)).ToList();
43+
if (actionKeys.Any())
44+
{
45+
Debug.Assert(actionKeys.Count == 1, $"{actionKeys.Count} Rename Actions have flag '{model.Target.DeclarationType.ToString()}'");
46+
_renameActions[actionKeys.FirstOrDefault()](model, rewriteSession);
47+
}
48+
else
49+
{
50+
RenameStandardElements(model.Target, model.NewName, rewriteSession);
51+
}
52+
}
53+
54+
private void RenameMember(RenameModel model, IRewriteSession rewriteSession)
55+
{
56+
if (model.Target.DeclarationType.HasFlag(DeclarationType.Property))
57+
{
58+
var members = _declarationFinderProvider.DeclarationFinder.MatchName(model.Target.IdentifierName)
59+
.Where(item => item.ProjectId == model.Target.ProjectId
60+
&& item.ComponentName == model.Target.ComponentName
61+
&& item.DeclarationType.HasFlag(DeclarationType.Property));
62+
63+
foreach (var member in members)
64+
{
65+
RenameStandardElements(member, model.NewName, rewriteSession);
66+
}
67+
}
68+
else
69+
{
70+
RenameStandardElements(model.Target, model.NewName, rewriteSession);
71+
}
72+
73+
if (!model.IsInterfaceMemberRename)
74+
{
75+
return;
76+
}
77+
78+
var implementations = _declarationFinderProvider.DeclarationFinder.FindAllInterfaceImplementingMembers()
79+
.Where(impl => ReferenceEquals(model.Target.ParentDeclaration, impl.InterfaceImplemented)
80+
&& impl.InterfaceMemberImplemented.IdentifierName.Equals(model.Target.IdentifierName));
81+
82+
RenameDefinedFormatMembers(model, implementations.ToList(), PrependUnderscoreFormat, rewriteSession);
83+
}
84+
85+
private void RenameParameter(RenameModel model, IRewriteSession rewriteSession)
86+
{
87+
if (model.Target.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Property))
88+
{
89+
var parameters = _declarationFinderProvider.DeclarationFinder.MatchName(model.Target.IdentifierName).Where(param =>
90+
param.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Property)
91+
&& param.DeclarationType == DeclarationType.Parameter
92+
&& param.ParentDeclaration.IdentifierName.Equals(model.Target.ParentDeclaration.IdentifierName)
93+
&& param.ParentDeclaration.ParentScopeDeclaration.Equals(model.Target.ParentDeclaration.ParentScopeDeclaration));
94+
95+
foreach (var param in parameters)
96+
{
97+
RenameStandardElements(param, model.NewName, rewriteSession);
98+
}
99+
}
100+
else
101+
{
102+
RenameStandardElements(model.Target, model.NewName, rewriteSession);
103+
}
104+
}
105+
106+
private void RenameEvent(RenameModel model, IRewriteSession rewriteSession)
107+
{
108+
RenameStandardElements(model.Target, model.NewName, rewriteSession);
109+
110+
var withEventsDeclarations = _declarationFinderProvider.DeclarationFinder.UserDeclarations(DeclarationType.Variable)
111+
.Where(varDec => varDec.IsWithEvents && varDec.AsTypeName.Equals(model.Target.ParentDeclaration.IdentifierName));
112+
113+
var eventHandlers = withEventsDeclarations.SelectMany(we => _declarationFinderProvider.DeclarationFinder.FindHandlersForWithEventsField(we));
114+
RenameDefinedFormatMembers(model, eventHandlers.ToList(), PrependUnderscoreFormat, rewriteSession);
115+
}
116+
117+
private void RenameVariable(RenameModel model, IRewriteSession rewriteSession)
118+
{
119+
if ((model.Target.Accessibility == Accessibility.Public ||
120+
model.Target.Accessibility == Accessibility.Implicit)
121+
&& model.Target.ParentDeclaration is ClassModuleDeclaration classDeclaration
122+
&& classDeclaration.Subtypes.Any())
123+
{
124+
RenameMember(model, rewriteSession);
125+
}
126+
else if (model.Target.DeclarationType.HasFlag(DeclarationType.Control))
127+
{
128+
var component = _projectsProvider.Component(model.Target.QualifiedName.QualifiedModuleName);
129+
using (var controls = component.Controls)
130+
{
131+
using (var control = controls.SingleOrDefault(item => item.Name == model.Target.IdentifierName))
132+
{
133+
Debug.Assert(control != null,
134+
$"input validation fail: unable to locate '{model.Target.IdentifierName}' in Controls collection");
135+
136+
control.Name = model.NewName;
137+
}
138+
}
139+
RenameReferences(model.Target, model.NewName, rewriteSession);
140+
var controlEventHandlers = FindEventHandlersForControl(model.Target);
141+
RenameDefinedFormatMembers(model, controlEventHandlers.ToList(), AppendUnderscoreFormat, rewriteSession);
142+
}
143+
else
144+
{
145+
RenameStandardElements(model.Target, model.NewName, rewriteSession);
146+
if (model.Target.IsWithEvents)
147+
{
148+
var eventHandlers = _declarationFinderProvider.DeclarationFinder.FindHandlersForWithEventsField(model.Target);
149+
RenameDefinedFormatMembers(model, eventHandlers.ToList(), AppendUnderscoreFormat, rewriteSession);
150+
}
151+
}
152+
}
153+
154+
private void RenameDefinedFormatMembers(RenameModel model, IReadOnlyCollection<Declaration> members, string underscoreFormat, IRewriteSession rewriteSession)
155+
{
156+
if (!members.Any()) { return; }
157+
158+
var targetFragment = string.Format(underscoreFormat, model.Target.IdentifierName);
159+
var replacementFragment = string.Format(underscoreFormat, model.NewName);
160+
foreach (var member in members)
161+
{
162+
var newMemberName = member.IdentifierName.Replace(targetFragment, replacementFragment);
163+
RenameStandardElements(member, newMemberName, rewriteSession);
164+
}
165+
}
166+
167+
private void RenameStandardElements(Declaration target, string newName, IRewriteSession rewriteSession)
168+
{
169+
RenameReferences(target, newName, rewriteSession);
170+
RenameDeclaration(target, newName, rewriteSession);
171+
}
172+
173+
private void RenameReferences(Declaration target, string newName, IRewriteSession rewriteSession)
174+
{
175+
var modules = target.References
176+
.Where(reference =>
177+
reference.Context.GetText() != "Me"
178+
&& !reference.IsArrayAccess
179+
&& !reference.IsDefaultMemberAccess)
180+
.GroupBy(r => r.QualifiedModuleName);
181+
182+
foreach (var grouping in modules)
183+
{
184+
var rewriter = rewriteSession.CheckOutModuleRewriter(grouping.Key);
185+
foreach (var reference in grouping)
186+
{
187+
rewriter.Replace(reference.Context, newName);
188+
}
189+
}
190+
}
191+
192+
private void RenameDeclaration(Declaration target, string newName, IRewriteSession rewriteSession)
193+
{
194+
var rewriter = rewriteSession.CheckOutModuleRewriter(target.QualifiedName.QualifiedModuleName);
195+
196+
if (target.Context is IIdentifierContext context)
197+
{
198+
rewriter.Replace(context.IdentifierTokens, newName);
199+
}
200+
}
201+
202+
private IEnumerable<Declaration> FindEventHandlersForControl(Declaration control)
203+
{
204+
if (control != null && control.DeclarationType.HasFlag(DeclarationType.Control))
205+
{
206+
return _declarationFinderProvider.DeclarationFinder.FindEventHandlers()
207+
.Where(ev => ev.Scope.StartsWith($"{control.ParentScope}.{control.IdentifierName}_"));
208+
}
209+
210+
return Enumerable.Empty<Declaration>();
211+
}
212+
}
213+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
using Rubberduck.Parsing;
5+
using Rubberduck.Parsing.Grammar;
6+
using Rubberduck.Parsing.Rewriter;
7+
using Rubberduck.Parsing.Symbols;
8+
using Rubberduck.Parsing.VBA;
9+
using Rubberduck.VBEditor.ComManagement;
10+
using Rubberduck.VBEditor.SafeComWrappers;
11+
12+
namespace Rubberduck.Refactorings.Rename
13+
{
14+
public class RenameComponentOrProjectRefactoringAction : RefactoringActionWithSuspension<RenameModel>
15+
{
16+
private const string AppendUnderscoreFormat = "{0}_";
17+
18+
private readonly IDeclarationFinderProvider _declarationFinderProvider;
19+
private readonly IProjectsProvider _projectsProvider;
20+
21+
public RenameComponentOrProjectRefactoringAction(
22+
IDeclarationFinderProvider declarationFinderProvider,
23+
IProjectsProvider projectsProvider,
24+
IParseManager parserManager,
25+
IRewritingManager rewritingManager)
26+
: base(parserManager, rewritingManager)
27+
{
28+
_declarationFinderProvider = declarationFinderProvider;
29+
_projectsProvider = projectsProvider;
30+
}
31+
32+
protected override bool RequiresSuspension(RenameModel model)
33+
{
34+
//The parser needs to be suspended during the refactoring of a component because the VBE API object rename causes a separate reparse.
35+
return true;
36+
}
37+
38+
protected override void Refactor(RenameModel model, IRewriteSession rewriteSession)
39+
{
40+
var targetDeclarationType = model.Target.DeclarationType;
41+
if (targetDeclarationType.HasFlag(DeclarationType.Module))
42+
{
43+
RenameModule(model, rewriteSession);
44+
}
45+
else if (targetDeclarationType.HasFlag(DeclarationType.Project))
46+
{
47+
RenameProject(model, rewriteSession);
48+
}
49+
}
50+
51+
private void RenameModule(RenameModel model, IRewriteSession rewriteSession)
52+
{
53+
RenameReferences(model.Target, model.NewName, rewriteSession);
54+
55+
if (model.Target.DeclarationType.HasFlag(DeclarationType.ClassModule))
56+
{
57+
foreach (var reference in model.Target.References)
58+
{
59+
var ctxt = reference.Context.GetAncestor<VBAParser.ImplementsStmtContext>();
60+
if (ctxt != null)
61+
{
62+
RenameDefinedFormatMembers(model, _declarationFinderProvider.DeclarationFinder.FindInterfaceMembersForImplementsContext(ctxt).ToList(), AppendUnderscoreFormat, rewriteSession);
63+
}
64+
}
65+
}
66+
67+
var component = _projectsProvider.Component(model.Target.QualifiedName.QualifiedModuleName);
68+
switch (component.Type)
69+
{
70+
case ComponentType.Document:
71+
{
72+
using (var properties = component.Properties)
73+
using (var property = properties["_CodeName"])
74+
{
75+
property.Value = model.NewName;
76+
}
77+
break;
78+
}
79+
case ComponentType.UserForm:
80+
case ComponentType.VBForm:
81+
case ComponentType.MDIForm:
82+
{
83+
using (var properties = component.Properties)
84+
using (var property = properties["Caption"])
85+
{
86+
if ((string)property.Value == model.Target.IdentifierName)
87+
{
88+
property.Value = model.NewName;
89+
}
90+
component.Name = model.NewName;
91+
}
92+
break;
93+
}
94+
default:
95+
{
96+
using (var vbe = component.VBE)
97+
{
98+
if (vbe.Kind == VBEKind.Hosted)
99+
{
100+
// VBA - rename code module
101+
using (var codeModule = component.CodeModule)
102+
{
103+
Debug.Assert(!codeModule.IsWrappingNullReference,
104+
"input validation fail: Attempting to rename an ICodeModule wrapping a null reference");
105+
codeModule.Name = model.NewName;
106+
}
107+
}
108+
else
109+
{
110+
// VB6 - rename component
111+
component.Name = model.NewName;
112+
}
113+
}
114+
break;
115+
}
116+
}
117+
}
118+
119+
//TODO: Implement renaming references to the project in code.
120+
private void RenameProject(RenameModel model, IRewriteSession rewriteSession)
121+
{
122+
var project = _projectsProvider.Project(model.Target.ProjectId);
123+
124+
if (project != null)
125+
{
126+
project.Name = model.NewName;
127+
}
128+
}
129+
130+
private void RenameDefinedFormatMembers(RenameModel model, IReadOnlyCollection<Declaration> members, string underscoreFormat, IRewriteSession rewriteSession)
131+
{
132+
if (!members.Any()) { return; }
133+
134+
var targetFragment = string.Format(underscoreFormat, model.Target.IdentifierName);
135+
var replacementFragment = string.Format(underscoreFormat, model.NewName);
136+
foreach (var member in members)
137+
{
138+
var newMemberName = member.IdentifierName.Replace(targetFragment, replacementFragment);
139+
RenameStandardElements(member, newMemberName, rewriteSession);
140+
}
141+
}
142+
143+
private void RenameStandardElements(Declaration target, string newName, IRewriteSession rewriteSession)
144+
{
145+
RenameReferences(target, newName, rewriteSession);
146+
RenameDeclaration(target, newName, rewriteSession);
147+
}
148+
149+
private void RenameReferences(Declaration target, string newName, IRewriteSession rewriteSession)
150+
{
151+
var modules = target.References
152+
.Where(reference =>
153+
reference.Context.GetText() != "Me"
154+
&& !reference.IsArrayAccess
155+
&& !reference.IsDefaultMemberAccess)
156+
.GroupBy(r => r.QualifiedModuleName);
157+
158+
foreach (var grouping in modules)
159+
{
160+
var rewriter = rewriteSession.CheckOutModuleRewriter(grouping.Key);
161+
foreach (var reference in grouping)
162+
{
163+
rewriter.Replace(reference.Context, newName);
164+
}
165+
}
166+
}
167+
168+
private void RenameDeclaration(Declaration target, string newName, IRewriteSession rewriteSession)
169+
{
170+
var rewriter = rewriteSession.CheckOutModuleRewriter(target.QualifiedName.QualifiedModuleName);
171+
172+
if (target.Context is IIdentifierContext context)
173+
{
174+
rewriter.Replace(context.IdentifierTokens, newName);
175+
}
176+
}
177+
}
178+
}

0 commit comments

Comments
 (0)