Skip to content

Commit 81902cc

Browse files
authored
Merge pull request #2357 from retailcoder/next
ImplementInterfaceRefactoring fix
2 parents f960479 + dc9e4ab commit 81902cc

File tree

9 files changed

+147
-93
lines changed

9 files changed

+147
-93
lines changed

RetailCoder.VBE/Refactorings/ImplementInterface/ImplementInterfaceRefactoring.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,19 @@ public void Refactor()
5151
Refactor(qualifiedSelection.Value);
5252
}
5353

54+
private static readonly IReadOnlyList<DeclarationType> ImplementingModuleTypes = new[]
55+
{
56+
DeclarationType.ClassModule,
57+
DeclarationType.UserForm,
58+
DeclarationType.Document,
59+
};
60+
5461
public void Refactor(QualifiedSelection selection)
5562
{
5663
_targetInterface = _declarations.FindInterface(selection);
5764

5865
_targetClass = _declarations.SingleOrDefault(d =>
59-
!d.IsBuiltIn && d.DeclarationType == DeclarationType.ClassModule &&
66+
!d.IsBuiltIn && ImplementingModuleTypes.Contains(d.DeclarationType) &&
6067
d.QualifiedSelection.QualifiedName.Equals(selection.QualifiedName));
6168

6269
if (_targetClass == null || _targetInterface == null)

Rubberduck.Parsing/Rubberduck.Parsing.csproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,6 @@
4646
<HintPath>..\packages\Antlr4.Runtime.4.3.0\lib\net45\Antlr4.Runtime.net45.dll</HintPath>
4747
</Reference>
4848
<Reference Include="Microsoft.Build.Tasks.v4.0" />
49-
<Reference Include="Microsoft.Vbe.Interop, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c">
50-
<SpecificVersion>False</SpecificVersion>
51-
<EmbedInteropTypes>False</EmbedInteropTypes>
52-
<HintPath>..\libs\Microsoft.Vbe.Interop.dll</HintPath>
53-
</Reference>
54-
<Reference Include="Microsoft.Vbe.Interop.Forms, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c, processorArchitecture=MSIL">
55-
<EmbedInteropTypes>False</EmbedInteropTypes>
56-
</Reference>
5749
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
5850
<HintPath>..\packages\NLog.4.0.1\lib\net45\NLog.dll</HintPath>
5951
<Private>True</Private>

Rubberduck.Parsing/VBA/ComponentParseTask.cs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
using System.Runtime.InteropServices;
1515
using System.Threading;
1616
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
17-
using CodeModule = Rubberduck.VBEditor.SafeComWrappers.VBA.CodeModule;
1817

1918
namespace Rubberduck.Parsing.VBA
2019
{
@@ -102,9 +101,64 @@ public void Start(CancellationToken token)
102101
}
103102
}
104103

104+
private static string[] GetSanitizedCode(ICodeModule module)
105+
{
106+
var lines = module.CountOfLines;
107+
if (lines == 0)
108+
{
109+
return new string[] { };
110+
}
111+
112+
var code = module.GetLines(1, lines).Replace("\r", string.Empty).Split('\n');
113+
114+
StripLineNumbers(code);
115+
return code;
116+
}
117+
118+
private static void StripLineNumbers(string[] lines)
119+
{
120+
var continuing = false;
121+
for (var line = 0; line < lines.Length; line++)
122+
{
123+
var code = lines[line];
124+
int? lineNumber;
125+
if (!continuing && HasNumberedLine(code, out lineNumber))
126+
{
127+
var lineNumberLength = lineNumber.ToString().Length;
128+
if (lines[line].Length > lineNumberLength)
129+
{
130+
// replace line number with as many spaces as characters taken, to avoid shifting the tokens
131+
lines[line] = new string(' ', lineNumberLength) + code.Substring(lineNumber.ToString().Length + 1);
132+
}
133+
}
134+
135+
continuing = code.EndsWith("_");
136+
}
137+
}
138+
139+
private static bool HasNumberedLine(string codeLine, out int? lineNumber)
140+
{
141+
lineNumber = null;
142+
143+
if (string.IsNullOrWhiteSpace(codeLine.Trim()))
144+
{
145+
return false;
146+
}
147+
148+
int line;
149+
var firstToken = codeLine.TrimStart().Split(' ')[0];
150+
if (int.TryParse(firstToken, out line))
151+
{
152+
lineNumber = line;
153+
return true;
154+
}
155+
156+
return false;
157+
}
158+
105159
private string RewriteAndPreprocess()
106160
{
107-
var code = _rewriter == null ? string.Join(Environment.NewLine, CodeModule.GetSanitizedCode(_component.CodeModule)) : _rewriter.GetText();
161+
var code = _rewriter == null ? string.Join(Environment.NewLine, GetSanitizedCode(_component.CodeModule)) : _rewriter.GetText();
108162
var processed = _preprocessor.Execute(_component.Name, code);
109163
return processed;
110164
}

Rubberduck.Parsing/VBA/ParseErrorEventArgs.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
using System;
22
using Rubberduck.Parsing.Symbols;
33
using Rubberduck.VBEditor;
4-
using Rubberduck.VBEditor.SafeComWrappers.VBA;
4+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
55

66
namespace Rubberduck.Parsing.VBA
77
{
88
public class ParseErrorEventArgs : EventArgs
99
{
10-
public ParseErrorEventArgs(SyntaxErrorException exception, VBComponent component)
10+
public ParseErrorEventArgs(SyntaxErrorException exception, IVBComponent component)
1111
{
1212
_exception = exception;
1313
_component = component;
@@ -16,7 +16,7 @@ public ParseErrorEventArgs(SyntaxErrorException exception, VBComponent component
1616
private readonly SyntaxErrorException _exception;
1717
public SyntaxErrorException Exception { get { return _exception; } }
1818

19-
private readonly VBComponent _component;
19+
private readonly IVBComponent _component;
2020
public string ComponentName { get { return _component.Name; } }
2121
public string ProjectName { get { return _component.Collection.Parent.Name; } }
2222

Rubberduck.Parsing/VBA/ReferencePriorityMap.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
using System.Collections.Generic;
2-
using Microsoft.Vbe.Interop;
32

43
namespace Rubberduck.Parsing.VBA
54
{
65
/// <summary>
7-
/// A <c>Dictionary</c> keyed with a <see cref="VBProject"/>'s ID and valued with an <see cref="int"/> representing a <see cref="Reference"/>'s priority for that project.
6+
/// A <c>Dictionary</c> keyed with a project's ID and valued with an int representing a reference's priority for that project.
87
/// </summary>
98
public class ReferencePriorityMap : Dictionary<string, int>
109
{

Rubberduck.Parsing/VBA/RubberduckParserState.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
using Rubberduck.VBEditor.Events;
1414
using Rubberduck.VBEditor.SafeComWrappers;
1515
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
16-
using Rubberduck.VBEditor.SafeComWrappers.VBA;
1716

1817
// ReSharper disable LoopCanBeConvertedToQuery
1918

@@ -449,7 +448,7 @@ private ParserState EvaluateParserState()
449448
return result;
450449
}
451450

452-
public ParserState GetOrCreateModuleState(VBComponent component)
451+
public ParserState GetOrCreateModuleState(IVBComponent component)
453452
{
454453
var key = new QualifiedModuleName(component);
455454
var state = _moduleStates.GetOrAdd(key, new ModuleState(ParserState.Pending)).State;

Rubberduck.VBEEditor/SafeComWrappers/Abstract/ICodeModule.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ public interface ICodeModule : ISafeComWrapper, IEquatable<ICodeModule>
1717
QualifiedSelection? GetQualifiedSelection();
1818
string Content();
1919
void Clear();
20-
string[] GetSanitizedCode();
2120
string ContentHash();
2221
void AddFromString(string content);
2322
void AddFromFile(string path);

Rubberduck.VBEEditor/SafeComWrappers/VBA/CodeModule.cs

Lines changed: 0 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -90,79 +90,6 @@ public void Clear()
9090
Target.DeleteLines(1, CountOfLines);
9191
}
9292

93-
public static string[] GetSanitizedCode(ICodeModule module)
94-
{
95-
var lines = module.CountOfLines;
96-
if (lines == 0)
97-
{
98-
return new string[] { };
99-
}
100-
101-
var code = module.GetLines(1, lines).Replace("\r", string.Empty).Split('\n');
102-
103-
StripLineNumbers(code);
104-
return code;
105-
}
106-
107-
/// <summary>
108-
/// Gets an array of strings where each element is a line of code in the Module,
109-
/// with line numbers stripped and any other pre-processing that needs to be done.
110-
/// </summary>
111-
public string[] GetSanitizedCode()
112-
{
113-
var lines = CountOfLines;
114-
if (lines == 0)
115-
{
116-
return new string[] { };
117-
}
118-
119-
var code = GetLines(1, lines).Replace("\r", string.Empty).Split('\n');
120-
121-
StripLineNumbers(code);
122-
return code;
123-
}
124-
125-
private static void StripLineNumbers(string[] lines)
126-
{
127-
var continuing = false;
128-
for (var line = 0; line < lines.Length; line++)
129-
{
130-
var code = lines[line];
131-
int? lineNumber;
132-
if (!continuing && HasNumberedLine(code, out lineNumber))
133-
{
134-
var lineNumberLength = lineNumber.ToString().Length;
135-
if (lines[line].Length > lineNumberLength)
136-
{
137-
// replace line number with as many spaces as characters taken, to avoid shifting the tokens
138-
lines[line] = new string(' ', lineNumberLength) + code.Substring(lineNumber.ToString().Length + 1);
139-
}
140-
}
141-
142-
continuing = code.EndsWith("_");
143-
}
144-
}
145-
146-
private static bool HasNumberedLine(string codeLine, out int? lineNumber)
147-
{
148-
lineNumber = null;
149-
150-
if (string.IsNullOrWhiteSpace(codeLine.Trim()))
151-
{
152-
return false;
153-
}
154-
155-
int line;
156-
var firstToken = codeLine.TrimStart().Split(' ')[0];
157-
if (int.TryParse(firstToken, out line))
158-
{
159-
lineNumber = line;
160-
return true;
161-
}
162-
163-
return false;
164-
}
165-
16693
private string _previousContentHash;
16794
public string ContentHash()
16895
{

RubberduckTests/Refactoring/ImplementInterfaceTests.cs

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ Public Property Let Buz(ByVal a As Boolean, ByRef value As Integer)
676676
const string inputCode2 =
677677
@"Implements Class1";
678678

679-
var selection = new Selection(1, 1, 1, 1);
679+
var selection = Selection.Home;
680680

681681
//Expectation
682682
const string expectedCode =
@@ -800,5 +800,82 @@ End Property
800800
//Assert
801801
Assert.AreEqual(expectedCode, module.Content());
802802
}
803+
804+
[TestMethod]
805+
public void ImplementsInterfaceInDocumentModule()
806+
{
807+
const string interfaceCode = @"Option Explicit
808+
Public Sub DoSomething()
809+
End Sub
810+
";
811+
const string initialCode = @"Implements IInterface";
812+
const string expectedCode = @"Implements IInterface
813+
814+
Private Sub IInterface_DoSomething()
815+
Err.Raise 5 'TODO implement interface member
816+
End Sub
817+
";
818+
var selection = Selection.Home;
819+
var vbe = new MockVbeBuilder()
820+
.ProjectBuilder("TestProject", ProjectProtection.Unprotected)
821+
.AddComponent("IInterface", ComponentType.ClassModule, interfaceCode)
822+
.AddComponent("Sheet1", ComponentType.Document, initialCode, selection)
823+
.MockVbeBuilder()
824+
.Build();
825+
826+
var project = vbe.Object.VBProjects[0];
827+
var component = project.VBComponents["Sheet1"];
828+
829+
var parser = MockParser.Create(vbe.Object, new RubberduckParserState(new Mock<ISinks>().Object));
830+
parser.Parse(new CancellationTokenSource());
831+
if (parser.State.Status >= ParserState.Error) { Assert.Inconclusive("Parser Error"); }
832+
833+
var qualifiedSelection = new QualifiedSelection(new QualifiedModuleName(component), Selection.Home);
834+
var module = component.CodeModule;
835+
836+
var refactoring = new ImplementInterfaceRefactoring(vbe.Object, parser.State, null);
837+
refactoring.Refactor(qualifiedSelection);
838+
839+
Assert.AreEqual(expectedCode, module.Content());
840+
}
841+
842+
[TestMethod]
843+
public void ImplementsInterfaceInUserFormModule()
844+
{
845+
const string interfaceCode = @"Option Explicit
846+
Public Sub DoSomething()
847+
End Sub
848+
";
849+
const string initialCode = @"Implements IInterface";
850+
const string expectedCode = @"Implements IInterface
851+
852+
Private Sub IInterface_DoSomething()
853+
Err.Raise 5 'TODO implement interface member
854+
End Sub
855+
";
856+
var selection = Selection.Home;
857+
var vbe = new MockVbeBuilder()
858+
.ProjectBuilder("TestProject", ProjectProtection.Unprotected)
859+
.AddComponent("IInterface", ComponentType.ClassModule, interfaceCode)
860+
.AddComponent("Form1", ComponentType.UserForm, initialCode, selection)
861+
.MockVbeBuilder()
862+
.Build();
863+
864+
var project = vbe.Object.VBProjects[0];
865+
var component = project.VBComponents["Form1"];
866+
867+
var parser = MockParser.Create(vbe.Object, new RubberduckParserState(new Mock<ISinks>().Object));
868+
parser.Parse(new CancellationTokenSource());
869+
if (parser.State.Status >= ParserState.Error) { Assert.Inconclusive("Parser Error"); }
870+
871+
var qualifiedSelection = new QualifiedSelection(new QualifiedModuleName(component), Selection.Home);
872+
var module = component.CodeModule;
873+
874+
var refactoring = new ImplementInterfaceRefactoring(vbe.Object, parser.State, null);
875+
refactoring.Refactor(qualifiedSelection);
876+
877+
Assert.AreEqual(expectedCode, module.Content());
878+
}
803879
}
804880
}
881+

0 commit comments

Comments
 (0)