Skip to content

Commit d26eef5

Browse files
committed
Enhance Implements positioning in ExtractInterfaceRefactoring
The implements statement is now placed right below the last already existing one or, in case there is none, below the last Option statement or, in case there is none, at the start of the module.
1 parent eb8dd7b commit d26eef5

File tree

2 files changed

+282
-13
lines changed

2 files changed

+282
-13
lines changed

Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceBaseRefactoring.cs

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using System;
22
using System.Linq;
3+
using Antlr4.Runtime;
4+
using Rubberduck.Parsing;
35
using Rubberduck.Parsing.Grammar;
46
using Rubberduck.Parsing.Rewriter;
57
using Rubberduck.Parsing.Symbols;
68
using Rubberduck.Parsing.VBA;
9+
using Rubberduck.Parsing.VBA.Parsing;
710
using Rubberduck.Refactorings.AddInterfaceImplementations;
811
using Rubberduck.VBEditor.SafeComWrappers;
912

@@ -12,17 +15,17 @@ namespace Rubberduck.Refactorings.ExtractInterface
1215
public class ExtractInterfaceBaseRefactoring : BaseRefactoringWithSuspensionBase<ExtractInterfaceModel>
1316
{
1417
private readonly ICodeOnlyBaseRefactoring<AddInterfaceImplementationsModel> _addImplementationsRefactoring;
15-
private readonly IDeclarationFinderProvider _declarationFinderProvider;
18+
private readonly IParseTreeProvider _parseTreeProvider;
1619

1720
public ExtractInterfaceBaseRefactoring(
1821
AddInterFaceImplementationsBaseRefactoring addImplementationsRefactoring,
19-
IDeclarationFinderProvider declarationFinderProvider,
22+
IParseTreeProvider parseTreeProvider,
2023
IParseManager parseManager,
2124
IRewritingManager rewritingManager)
2225
: base(parseManager, rewritingManager)
2326
{
2427
_addImplementationsRefactoring = addImplementationsRefactoring;
25-
_declarationFinderProvider = declarationFinderProvider;
28+
_parseTreeProvider = parseTreeProvider;
2629
}
2730

2831
protected override bool RequiresSuspension(ExtractInterfaceModel model)
@@ -44,14 +47,7 @@ private void AddInterface(ExtractInterfaceModel model, IRewriteSession rewriteSe
4447
}
4548

4649
AddInterfaceClass(model.TargetDeclaration, model.InterfaceName, GetInterfaceModuleBody(model));
47-
48-
var rewriter = rewriteSession.CheckOutModuleRewriter(model.TargetDeclaration.QualifiedModuleName);
49-
50-
var firstNonFieldMember = _declarationFinderProvider.DeclarationFinder.Members(model.TargetDeclaration)
51-
.OrderBy(o => o.Selection)
52-
.First(m => ExtractInterfaceModel.MemberTypes.Contains(m.DeclarationType));
53-
rewriter.InsertBefore(firstNonFieldMember.Context.Start.TokenIndex, $"Implements {model.InterfaceName}{Environment.NewLine}{Environment.NewLine}");
54-
50+
AddImplementsStatement(model, rewriteSession);
5551
AddInterfaceMembersToClass(model, rewriteSession);
5652
}
5753

@@ -77,6 +73,55 @@ private void AddInterfaceClass(Declaration implementingClass, string interfaceNa
7773
}
7874
}
7975

76+
private void AddImplementsStatement(ExtractInterfaceModel model, IRewriteSession rewriteSession)
77+
{
78+
var rewriter = rewriteSession.CheckOutModuleRewriter(model.TargetDeclaration.QualifiedModuleName);
79+
80+
var implementsStatement = $"Implements {model.InterfaceName}";
81+
82+
var (insertionIndex, isImplementsStatement) = InsertionIndex(model);
83+
84+
if (insertionIndex == -1)
85+
{
86+
rewriter.InsertBefore(0, $"{implementsStatement}{Environment.NewLine}{Environment.NewLine}");
87+
}
88+
else
89+
{
90+
rewriter.InsertAfter(insertionIndex, $"{Environment.NewLine}{(isImplementsStatement ? string.Empty : Environment.NewLine)}{implementsStatement}");
91+
}
92+
}
93+
94+
private (int index, bool isImplementsStatement) InsertionIndex(ExtractInterfaceModel model)
95+
{
96+
var tree = (ParserRuleContext)_parseTreeProvider.GetParseTree(model.TargetDeclaration.QualifiedModuleName, CodeKind.CodePaneCode);
97+
98+
var moduleDeclarations = tree.GetDescendent<VBAParser.ModuleDeclarationsContext>();
99+
if (moduleDeclarations == null)
100+
{
101+
return (-1, false);
102+
}
103+
104+
var lastImplementsStatement = moduleDeclarations
105+
.GetDescendents<VBAParser.ImplementsStmtContext>()
106+
.LastOrDefault();
107+
if (lastImplementsStatement != null)
108+
{
109+
return (lastImplementsStatement.Stop.TokenIndex, true);
110+
}
111+
112+
var lastOptionStatement = moduleDeclarations
113+
.GetDescendents<VBAParser.ModuleOptionContext>()
114+
.LastOrDefault();
115+
if (lastOptionStatement != null)
116+
{
117+
return (lastOptionStatement.Stop.TokenIndex, false);
118+
}
119+
120+
return (-1, false);
121+
}
122+
123+
124+
80125
private void AddInterfaceMembersToClass(ExtractInterfaceModel model, IRewriteSession rewriteSession)
81126
{
82127
var targetModule = model.TargetDeclaration.QualifiedModuleName;

RubberduckTests/Refactoring/ExtractInterfaceTests.cs

Lines changed: 226 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ Public Property Set Buzz(value)
152152
var selection = new Selection(1, 23, 1, 27);
153153

154154
//Expectation
155-
const string expectedCode = @"
156-
Implements IClass
155+
const string expectedCode = @"Implements IClass
156+
157157
158158
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
159159
End Sub
@@ -419,6 +419,230 @@ End Sub
419419
Assert.AreEqual(expectedInterfaceCode, actualInterfaceCode);
420420
}
421421

422+
[Test]
423+
[Category("Refactorings")]
424+
[Category("Extract Interface")]
425+
public void ExtractInterfaceRefactoring_BelowLastImplementStatement()
426+
{
427+
//Input
428+
const string inputCode =
429+
@"
430+
431+
Option Explicit
432+
433+
Implements Interface1
434+
435+
436+
Implements Interface2
437+
438+
439+
440+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
441+
End Sub";
442+
var selection = new Selection(1, 23, 1, 27);
443+
444+
//Expectation
445+
const string expectedCode =
446+
@"
447+
448+
Option Explicit
449+
450+
Implements Interface1
451+
452+
453+
Implements Interface2
454+
Implements IClass
455+
456+
457+
458+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
459+
End Sub
460+
461+
Private Sub IClass_Foo(ByVal arg1 As Integer, ByVal arg2 As String)
462+
Err.Raise 5 'TODO implement interface member
463+
End Sub
464+
";
465+
466+
const string expectedInterfaceCode =
467+
@"Option Explicit
468+
469+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
470+
End Sub
471+
472+
";
473+
Func<ExtractInterfaceModel, ExtractInterfaceModel> presenterAction = model =>
474+
{
475+
foreach (var interfaceMember in model.Members)
476+
{
477+
interfaceMember.IsSelected = true;
478+
}
479+
480+
return model;
481+
};
482+
483+
var actualCode = RefactoredCode("Class", selection, presenterAction, null, false,
484+
("Class", inputCode, ComponentType.ClassModule),
485+
("Interface1", string.Empty, ComponentType.ClassModule),
486+
("Interface2", string.Empty, ComponentType.ClassModule));
487+
Assert.AreEqual(expectedCode, actualCode["Class"]);
488+
var actualInterfaceCode = actualCode[actualCode.Keys.Single(componentName => !new[]{"Class", "Interface1", "Interface2"}.Contains(componentName))];
489+
Assert.AreEqual(expectedInterfaceCode, actualInterfaceCode);
490+
}
491+
492+
[Test]
493+
[Category("Refactorings")]
494+
[Category("Extract Interface")]
495+
public void ExtractInterfaceRefactoring_BelowLastOptionStatement()
496+
{
497+
//Input
498+
const string inputCode =
499+
@"
500+
501+
Option Explicit
502+
503+
504+
505+
Option Base 1
506+
507+
508+
509+
510+
511+
Private bar As Variant
512+
513+
514+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
515+
End Sub";
516+
var selection = new Selection(1, 23, 1, 27);
517+
518+
//Expectation
519+
const string expectedCode =
520+
@"
521+
522+
Option Explicit
523+
524+
525+
526+
Option Base 1
527+
528+
Implements IClass
529+
530+
531+
532+
533+
534+
Private bar As Variant
535+
536+
537+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
538+
End Sub
539+
540+
Private Sub IClass_Foo(ByVal arg1 As Integer, ByVal arg2 As String)
541+
Err.Raise 5 'TODO implement interface member
542+
End Sub
543+
";
544+
545+
const string expectedInterfaceCode =
546+
@"Option Explicit
547+
548+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
549+
End Sub
550+
551+
";
552+
Func<ExtractInterfaceModel, ExtractInterfaceModel> presenterAction = model =>
553+
{
554+
foreach (var interfaceMember in model.Members)
555+
{
556+
interfaceMember.IsSelected = true;
557+
}
558+
559+
return model;
560+
};
561+
562+
var actualCode = RefactoredCode("Class", selection, presenterAction, null, false,
563+
("Class", inputCode, ComponentType.ClassModule));
564+
Assert.AreEqual(expectedCode, actualCode["Class"]);
565+
var actualInterfaceCode = actualCode[actualCode.Keys.Single(componentName => !componentName.Equals("Class"))];
566+
Assert.AreEqual(expectedInterfaceCode, actualInterfaceCode);
567+
}
568+
569+
[Test]
570+
[Category("Refactorings")]
571+
[Category("Extract Interface")]
572+
public void ExtractInterfaceRefactoring_AtTopOfModule()
573+
{
574+
//Input
575+
const string inputCode =
576+
@"
577+
578+
579+
580+
581+
582+
583+
584+
585+
586+
587+
588+
Private bar As Variant
589+
590+
591+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
592+
End Sub";
593+
var selection = new Selection(1, 23, 1, 27);
594+
595+
//Expectation
596+
const string expectedCode =
597+
@"Implements IClass
598+
599+
600+
601+
602+
603+
604+
605+
606+
607+
608+
609+
610+
611+
Private bar As Variant
612+
613+
614+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
615+
End Sub
616+
617+
Private Sub IClass_Foo(ByVal arg1 As Integer, ByVal arg2 As String)
618+
Err.Raise 5 'TODO implement interface member
619+
End Sub
620+
";
621+
622+
const string expectedInterfaceCode =
623+
@"Option Explicit
624+
625+
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
626+
End Sub
627+
628+
";
629+
Func<ExtractInterfaceModel, ExtractInterfaceModel> presenterAction = model =>
630+
{
631+
foreach (var interfaceMember in model.Members)
632+
{
633+
interfaceMember.IsSelected = true;
634+
}
635+
636+
return model;
637+
};
638+
639+
var actualCode = RefactoredCode("Class", selection, presenterAction, null, false,
640+
("Class", inputCode, ComponentType.ClassModule));
641+
Assert.AreEqual(expectedCode, actualCode["Class"]);
642+
var actualInterfaceCode = actualCode[actualCode.Keys.Single(componentName => !componentName.Equals("Class"))];
643+
Assert.AreEqual(expectedInterfaceCode, actualInterfaceCode);
644+
}
645+
422646
protected override IRefactoring TestRefactoring(IRewritingManager rewritingManager, RubberduckParserState state, IRefactoringPresenterFactory factory, ISelectionService selectionService)
423647
{
424648
var uiDispatcherMock = new Mock<IUiDispatcher>();

0 commit comments

Comments
 (0)