Skip to content

Commit 0b02dfe

Browse files
committed
Add ExpandBangNotationQuickFix
1 parent 0d803d2 commit 0b02dfe

File tree

6 files changed

+298
-1
lines changed

6 files changed

+298
-1
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.Linq;
2+
using Antlr4.Runtime;
3+
using Rubberduck.Inspections.Abstract;
4+
using Rubberduck.Inspections.Concrete;
5+
using Rubberduck.Parsing.Grammar;
6+
using Rubberduck.Parsing.Inspections.Abstract;
7+
using Rubberduck.Parsing.Rewriter;
8+
using Rubberduck.Parsing.VBA;
9+
using Rubberduck.Parsing.VBA.DeclarationCaching;
10+
using Rubberduck.VBEditor;
11+
12+
namespace Rubberduck.CodeAnalysis.QuickFixes
13+
{
14+
public class ExpandBangNotationQuickFix : QuickFixBase
15+
{
16+
private readonly IDeclarationFinderProvider _declarationFinderProvider;
17+
18+
public ExpandBangNotationQuickFix(IDeclarationFinderProvider declarationFinderProvider)
19+
: base(typeof(UseOfBangNotationInspection), typeof(UseOfRecursiveBangNotationInspection))
20+
{
21+
_declarationFinderProvider = declarationFinderProvider;
22+
}
23+
24+
public override void Fix(IInspectionResult result, IRewriteSession rewriteSession)
25+
{
26+
var rewriter = rewriteSession.CheckOutModuleRewriter(result.QualifiedSelection.QualifiedName);
27+
28+
var dictionaryAccessContext = (VBAParser.DictionaryAccessContext)result.Context;
29+
AdjustArgument(dictionaryAccessContext, rewriter);
30+
31+
var finder = _declarationFinderProvider.DeclarationFinder;
32+
var selection = result.QualifiedSelection;
33+
InsertDefaultMember(dictionaryAccessContext, selection, finder, rewriter);
34+
}
35+
36+
private void AdjustArgument(VBAParser.DictionaryAccessContext dictionaryAccessContext, IModuleRewriter rewriter)
37+
{
38+
var argumentContext = ArgumentContext(dictionaryAccessContext);
39+
rewriter.InsertBefore(argumentContext.Start.TokenIndex, "(\"");
40+
rewriter.InsertAfter(argumentContext.Stop.TokenIndex, "\")");
41+
}
42+
43+
private ParserRuleContext ArgumentContext(VBAParser.DictionaryAccessContext dictionaryAccessContext)
44+
{
45+
if (dictionaryAccessContext.parent is VBAParser.DictionaryAccessExprContext dictionaryAccess)
46+
{
47+
return dictionaryAccess.unrestrictedIdentifier();
48+
}
49+
50+
return ((VBAParser.WithDictionaryAccessExprContext) dictionaryAccessContext.parent).unrestrictedIdentifier();
51+
}
52+
53+
private void InsertDefaultMember(VBAParser.DictionaryAccessContext dictionaryAccessContext, QualifiedSelection selection, DeclarationFinder finder, IModuleRewriter rewriter)
54+
{
55+
var defaultMemberAccessCode = DefaultMemberAccessCode(selection, finder);
56+
rewriter.Replace(dictionaryAccessContext, defaultMemberAccessCode);
57+
}
58+
59+
private string DefaultMemberAccessCode(QualifiedSelection selection, DeclarationFinder finder)
60+
{
61+
var defaultMemberAccesses = finder.IdentifierReferences(selection);
62+
var defaultMemberNames = defaultMemberAccesses.Select(reference => reference.Declaration.IdentifierName);
63+
return $".{string.Join("().", defaultMemberNames)}";
64+
}
65+
66+
public override string Description(IInspectionResult result)
67+
{
68+
return Resources.Inspections.QuickFixes.ExpandBangNotationQuickFix;
69+
}
70+
71+
public override bool CanFixInProcedure => true;
72+
public override bool CanFixInModule => true;
73+
public override bool CanFixInProject => true;
74+
}
75+
}

Rubberduck.Resources/Inspections/QuickFixes.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Rubberduck.Resources/Inspections/QuickFixes.de.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,7 @@
291291
<data name="ReplaceWhileWendWithDoWhileLoopQuickFix" xml:space="preserve">
292292
<value>'While...Wend' durch'Do While...Loop' ersetzen</value>
293293
</data>
294+
<data name="ExpandBangNotationQuickFix" xml:space="preserve">
295+
<value>Ersetze die Ausrufezeichennotation durch einen expliziten Aufruf</value>
296+
</data>
294297
</root>

Rubberduck.Resources/Inspections/QuickFixes.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,4 +291,7 @@
291291
<data name="ReplaceWhileWendWithDoWhileLoopQuickFix" xml:space="preserve">
292292
<value>Replace 'While...Wend' with 'Do While...Loop'</value>
293293
</data>
294+
<data name="ExpandBangNotationQuickFix" xml:space="preserve">
295+
<value>Replace bang notation with explicit access</value>
296+
</data>
294297
</root>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
using NUnit.Framework;
2+
using Rubberduck.CodeAnalysis.QuickFixes;
3+
using Rubberduck.Inspections.Concrete;
4+
using Rubberduck.Parsing.Inspections.Abstract;
5+
using Rubberduck.Parsing.VBA;
6+
using Rubberduck.Parsing.VBA.Parsing;
7+
using Rubberduck.VBEditor.SafeComWrappers;
8+
using RubberduckTests.Mocks;
9+
10+
namespace RubberduckTests.QuickFixes
11+
{
12+
public class ExpandBangNotationQuickFixTests : QuickFixTestBase
13+
{
14+
[Test]
15+
[Category("QuickFixes")]
16+
public void DictionaryAccessExpression_QuickFixWorks()
17+
{
18+
var class1Code = @"
19+
Public Function Foo(bar As String) As Class2
20+
Attribute Foo.VB_UserMemId = 0
21+
End Function
22+
";
23+
24+
var class2Code = @"
25+
Public Function Baz(bar As String) As Class2
26+
Attribute Baz.VB_UserMemId = 0
27+
End Function
28+
";
29+
30+
var moduleCode = @"
31+
Private Function Foo() As Class2
32+
Dim cls As New Class1
33+
Set Foo = cls!newClassObject
34+
End Function
35+
";
36+
37+
var expectedModuleCode = @"
38+
Private Function Foo() As Class2
39+
Dim cls As New Class1
40+
Set Foo = cls.Foo(""newClassObject"")
41+
End Function
42+
";
43+
44+
var vbe = MockVbeBuilder.BuildFromModules(
45+
("Class1", class1Code, ComponentType.ClassModule),
46+
("Class2", class2Code, ComponentType.ClassModule),
47+
("Module1", moduleCode, ComponentType.StandardModule));
48+
49+
var actualModuleCode = ApplyQuickFixToFirstInspectionResult(vbe.Object, "Module1", state => new UseOfBangNotationInspection(state), CodeKind.AttributesCode);
50+
Assert.AreEqual(expectedModuleCode, actualModuleCode);
51+
}
52+
[Test]
53+
[Category("QuickFixes")]
54+
public void RecursiveDictionaryAccessExpression_QuickFixWorks()
55+
{
56+
var class1Code = @"
57+
Public Function Foo() As Class2
58+
Attribute Foo.VB_UserMemId = 0
59+
End Function
60+
";
61+
62+
var class2Code = @"
63+
Public Function Baz(bar As String) As Class2
64+
Attribute Baz.VB_UserMemId = 0
65+
End Function
66+
";
67+
68+
var moduleCode = @"
69+
Private Function Foo() As Class2
70+
Dim cls As New Class1
71+
Set Foo = cls!newClassObject
72+
End Function
73+
";
74+
75+
var expectedModuleCode = @"
76+
Private Function Foo() As Class2
77+
Dim cls As New Class1
78+
Set Foo = cls.Foo().Baz(""newClassObject"")
79+
End Function
80+
";
81+
82+
var vbe = MockVbeBuilder.BuildFromModules(
83+
("Class1", class1Code, ComponentType.ClassModule),
84+
("Class2", class2Code, ComponentType.ClassModule),
85+
("Module1", moduleCode, ComponentType.StandardModule));
86+
87+
var actualModuleCode = ApplyQuickFixToFirstInspectionResult(vbe.Object, "Module1", state => new UseOfRecursiveBangNotationInspection(state), CodeKind.AttributesCode);
88+
Assert.AreEqual(expectedModuleCode, actualModuleCode);
89+
}
90+
[Test]
91+
[Category("QuickFixes")]
92+
public void WithDictionaryAccessExpression_QuickFixWorks()
93+
{
94+
var class1Code = @"
95+
Public Function Foo(bar As String) As Class2
96+
Attribute Foo.VB_UserMemId = 0
97+
End Function
98+
";
99+
100+
var class2Code = @"
101+
Public Function Baz(bar As String) As Class2
102+
Attribute Baz.VB_UserMemId = 0
103+
End Function
104+
";
105+
106+
var moduleCode = @"
107+
Private Function Foo() As Class2
108+
With New Class1
109+
Set Foo = !newClassObject
110+
End With
111+
End Function
112+
";
113+
114+
var expectedModuleCode = @"
115+
Private Function Foo() As Class2
116+
With New Class1
117+
Set Foo = .Foo(""newClassObject"")
118+
End With
119+
End Function
120+
";
121+
122+
var vbe = MockVbeBuilder.BuildFromModules(
123+
("Class1", class1Code, ComponentType.ClassModule),
124+
("Class2", class2Code, ComponentType.ClassModule),
125+
("Module1", moduleCode, ComponentType.StandardModule));
126+
127+
var actualModuleCode = ApplyQuickFixToFirstInspectionResult(vbe.Object, "Module1", state => new UseOfBangNotationInspection(state), CodeKind.AttributesCode);
128+
Assert.AreEqual(expectedModuleCode, actualModuleCode);
129+
}
130+
[Test]
131+
[Category("QuickFixes")]
132+
public void RecursiveWithDictionaryAccessExpression_QuickFixWorks()
133+
{
134+
var class1Code = @"
135+
Public Function Foo() As Class2
136+
Attribute Foo.VB_UserMemId = 0
137+
End Function
138+
";
139+
140+
var class2Code = @"
141+
Public Function Baz(bar As String) As Class2
142+
Attribute Baz.VB_UserMemId = 0
143+
End Function
144+
";
145+
146+
var moduleCode = @"
147+
Private Function Foo() As Class2
148+
With New Class1
149+
Set Foo = !newClassObject
150+
End With
151+
End Function
152+
";
153+
154+
var expectedModuleCode = @"
155+
Private Function Foo() As Class2
156+
With New Class1
157+
Set Foo = .Foo().Baz(""newClassObject"")
158+
End With
159+
End Function
160+
";
161+
162+
var vbe = MockVbeBuilder.BuildFromModules(
163+
("Class1", class1Code, ComponentType.ClassModule),
164+
("Class2", class2Code, ComponentType.ClassModule),
165+
("Module1", moduleCode, ComponentType.StandardModule));
166+
167+
var actualModuleCode = ApplyQuickFixToFirstInspectionResult(vbe.Object, "Module1", state => new UseOfRecursiveBangNotationInspection(state), CodeKind.AttributesCode);
168+
Assert.AreEqual(expectedModuleCode, actualModuleCode);
169+
}
170+
171+
protected override IQuickFix QuickFix(RubberduckParserState state)
172+
{
173+
return new ExpandBangNotationQuickFix(state);
174+
}
175+
}
176+
}

RubberduckTests/QuickFixes/QuickFixTestBase.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,41 @@ protected string ApplyQuickFixToFirstInspectionResult(string inputCode,
3434
codeKind);
3535
}
3636

37+
protected string ApplyQuickFixToFirstInspectionResult(
38+
IVBE vbe,
39+
string componentName,
40+
Func<RubberduckParserState, IInspection> inspectionFactory,
41+
CodeKind codeKind = CodeKind.CodePaneCode)
42+
{
43+
return ApplyQuickFixToAppropriateInspectionResults(
44+
vbe,
45+
componentName,
46+
inspectionFactory,
47+
ApplyToFirstResult,
48+
codeKind);
49+
}
50+
3751
private string ApplyQuickFixToAppropriateInspectionResults(string inputCode,
3852
Func<RubberduckParserState, IInspection> inspectionFactory,
3953
Action<IQuickFix, IEnumerable<IInspectionResult>, IRewriteSession> applyQuickFix,
4054
CodeKind codeKind)
4155
{
4256
var vbe = TestVbe(inputCode, out var component);
57+
return ApplyQuickFixToAppropriateInspectionResults(
58+
vbe,
59+
component.Name,
60+
inspectionFactory,
61+
applyQuickFix,
62+
codeKind);
63+
}
64+
65+
private string ApplyQuickFixToAppropriateInspectionResults(
66+
IVBE vbe,
67+
string componentName,
68+
Func<RubberduckParserState, IInspection> inspectionFactory,
69+
Action<IQuickFix, IEnumerable<IInspectionResult>, IRewriteSession> applyQuickFix,
70+
CodeKind codeKind)
71+
{
4372
var (state, rewriteManager) = MockParser.CreateAndParseWithRewritingManager(vbe);
4473
using (state)
4574
{
@@ -53,7 +82,9 @@ private string ApplyQuickFixToAppropriateInspectionResults(string inputCode,
5382

5483
applyQuickFix(quickFix, inspectionResults, rewriteSession);
5584

56-
return rewriteSession.CheckOutModuleRewriter(component.QualifiedModuleName).GetText();
85+
var module = state.DeclarationFinder.AllModules.First(qmn => qmn.ComponentName == componentName);
86+
87+
return rewriteSession.CheckOutModuleRewriter(module).GetText();
5788
}
5889
}
5990

0 commit comments

Comments
 (0)