Skip to content

Commit

Permalink
Enhance Implements positioning in ExtractInterfaceRefactoring
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
MDoerner committed Feb 16, 2020
1 parent eb8dd7b commit d26eef5
Show file tree
Hide file tree
Showing 2 changed files with 282 additions and 13 deletions.
@@ -1,9 +1,12 @@
using System;
using System.Linq;
using Antlr4.Runtime;
using Rubberduck.Parsing;
using Rubberduck.Parsing.Grammar;
using Rubberduck.Parsing.Rewriter;
using Rubberduck.Parsing.Symbols;
using Rubberduck.Parsing.VBA;
using Rubberduck.Parsing.VBA.Parsing;
using Rubberduck.Refactorings.AddInterfaceImplementations;
using Rubberduck.VBEditor.SafeComWrappers;

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

public ExtractInterfaceBaseRefactoring(
AddInterFaceImplementationsBaseRefactoring addImplementationsRefactoring,
IDeclarationFinderProvider declarationFinderProvider,
IParseTreeProvider parseTreeProvider,
IParseManager parseManager,
IRewritingManager rewritingManager)
: base(parseManager, rewritingManager)
{
_addImplementationsRefactoring = addImplementationsRefactoring;
_declarationFinderProvider = declarationFinderProvider;
_parseTreeProvider = parseTreeProvider;
}

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

AddInterfaceClass(model.TargetDeclaration, model.InterfaceName, GetInterfaceModuleBody(model));

var rewriter = rewriteSession.CheckOutModuleRewriter(model.TargetDeclaration.QualifiedModuleName);

var firstNonFieldMember = _declarationFinderProvider.DeclarationFinder.Members(model.TargetDeclaration)
.OrderBy(o => o.Selection)
.First(m => ExtractInterfaceModel.MemberTypes.Contains(m.DeclarationType));
rewriter.InsertBefore(firstNonFieldMember.Context.Start.TokenIndex, $"Implements {model.InterfaceName}{Environment.NewLine}{Environment.NewLine}");

AddImplementsStatement(model, rewriteSession);
AddInterfaceMembersToClass(model, rewriteSession);
}

Expand All @@ -77,6 +73,55 @@ private void AddInterfaceClass(Declaration implementingClass, string interfaceNa
}
}

private void AddImplementsStatement(ExtractInterfaceModel model, IRewriteSession rewriteSession)
{
var rewriter = rewriteSession.CheckOutModuleRewriter(model.TargetDeclaration.QualifiedModuleName);

var implementsStatement = $"Implements {model.InterfaceName}";

var (insertionIndex, isImplementsStatement) = InsertionIndex(model);

if (insertionIndex == -1)
{
rewriter.InsertBefore(0, $"{implementsStatement}{Environment.NewLine}{Environment.NewLine}");
}
else
{
rewriter.InsertAfter(insertionIndex, $"{Environment.NewLine}{(isImplementsStatement ? string.Empty : Environment.NewLine)}{implementsStatement}");
}
}

private (int index, bool isImplementsStatement) InsertionIndex(ExtractInterfaceModel model)
{
var tree = (ParserRuleContext)_parseTreeProvider.GetParseTree(model.TargetDeclaration.QualifiedModuleName, CodeKind.CodePaneCode);

var moduleDeclarations = tree.GetDescendent<VBAParser.ModuleDeclarationsContext>();
if (moduleDeclarations == null)
{
return (-1, false);
}

var lastImplementsStatement = moduleDeclarations
.GetDescendents<VBAParser.ImplementsStmtContext>()
.LastOrDefault();
if (lastImplementsStatement != null)
{
return (lastImplementsStatement.Stop.TokenIndex, true);
}

var lastOptionStatement = moduleDeclarations
.GetDescendents<VBAParser.ModuleOptionContext>()
.LastOrDefault();
if (lastOptionStatement != null)
{
return (lastOptionStatement.Stop.TokenIndex, false);
}

return (-1, false);
}



private void AddInterfaceMembersToClass(ExtractInterfaceModel model, IRewriteSession rewriteSession)
{
var targetModule = model.TargetDeclaration.QualifiedModuleName;
Expand Down
228 changes: 226 additions & 2 deletions RubberduckTests/Refactoring/ExtractInterfaceTests.cs
Expand Up @@ -152,8 +152,8 @@ End Property
var selection = new Selection(1, 23, 1, 27);

//Expectation
const string expectedCode = @"
Implements IClass
const string expectedCode = @"Implements IClass
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub
Expand Down Expand Up @@ -419,6 +419,230 @@ End Sub
Assert.AreEqual(expectedInterfaceCode, actualInterfaceCode);
}

[Test]
[Category("Refactorings")]
[Category("Extract Interface")]
public void ExtractInterfaceRefactoring_BelowLastImplementStatement()
{
//Input
const string inputCode =
@"
Option Explicit
Implements Interface1
Implements Interface2
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub";
var selection = new Selection(1, 23, 1, 27);

//Expectation
const string expectedCode =
@"
Option Explicit
Implements Interface1
Implements Interface2
Implements IClass
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub
Private Sub IClass_Foo(ByVal arg1 As Integer, ByVal arg2 As String)
Err.Raise 5 'TODO implement interface member
End Sub
";

const string expectedInterfaceCode =
@"Option Explicit
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub
";
Func<ExtractInterfaceModel, ExtractInterfaceModel> presenterAction = model =>
{
foreach (var interfaceMember in model.Members)
{
interfaceMember.IsSelected = true;
}
return model;
};

var actualCode = RefactoredCode("Class", selection, presenterAction, null, false,
("Class", inputCode, ComponentType.ClassModule),
("Interface1", string.Empty, ComponentType.ClassModule),
("Interface2", string.Empty, ComponentType.ClassModule));
Assert.AreEqual(expectedCode, actualCode["Class"]);
var actualInterfaceCode = actualCode[actualCode.Keys.Single(componentName => !new[]{"Class", "Interface1", "Interface2"}.Contains(componentName))];
Assert.AreEqual(expectedInterfaceCode, actualInterfaceCode);
}

[Test]
[Category("Refactorings")]
[Category("Extract Interface")]
public void ExtractInterfaceRefactoring_BelowLastOptionStatement()
{
//Input
const string inputCode =
@"
Option Explicit
Option Base 1
Private bar As Variant
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub";
var selection = new Selection(1, 23, 1, 27);

//Expectation
const string expectedCode =
@"
Option Explicit
Option Base 1
Implements IClass
Private bar As Variant
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub
Private Sub IClass_Foo(ByVal arg1 As Integer, ByVal arg2 As String)
Err.Raise 5 'TODO implement interface member
End Sub
";

const string expectedInterfaceCode =
@"Option Explicit
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub
";
Func<ExtractInterfaceModel, ExtractInterfaceModel> presenterAction = model =>
{
foreach (var interfaceMember in model.Members)
{
interfaceMember.IsSelected = true;
}
return model;
};

var actualCode = RefactoredCode("Class", selection, presenterAction, null, false,
("Class", inputCode, ComponentType.ClassModule));
Assert.AreEqual(expectedCode, actualCode["Class"]);
var actualInterfaceCode = actualCode[actualCode.Keys.Single(componentName => !componentName.Equals("Class"))];
Assert.AreEqual(expectedInterfaceCode, actualInterfaceCode);
}

[Test]
[Category("Refactorings")]
[Category("Extract Interface")]
public void ExtractInterfaceRefactoring_AtTopOfModule()
{
//Input
const string inputCode =
@"
Private bar As Variant
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub";
var selection = new Selection(1, 23, 1, 27);

//Expectation
const string expectedCode =
@"Implements IClass
Private bar As Variant
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub
Private Sub IClass_Foo(ByVal arg1 As Integer, ByVal arg2 As String)
Err.Raise 5 'TODO implement interface member
End Sub
";

const string expectedInterfaceCode =
@"Option Explicit
Public Sub Foo(ByVal arg1 As Integer, ByVal arg2 As String)
End Sub
";
Func<ExtractInterfaceModel, ExtractInterfaceModel> presenterAction = model =>
{
foreach (var interfaceMember in model.Members)
{
interfaceMember.IsSelected = true;
}
return model;
};

var actualCode = RefactoredCode("Class", selection, presenterAction, null, false,
("Class", inputCode, ComponentType.ClassModule));
Assert.AreEqual(expectedCode, actualCode["Class"]);
var actualInterfaceCode = actualCode[actualCode.Keys.Single(componentName => !componentName.Equals("Class"))];
Assert.AreEqual(expectedInterfaceCode, actualInterfaceCode);
}

protected override IRefactoring TestRefactoring(IRewritingManager rewritingManager, RubberduckParserState state, IRefactoringPresenterFactory factory, ISelectionService selectionService)
{
var uiDispatcherMock = new Mock<IUiDispatcher>();
Expand Down

0 comments on commit d26eef5

Please sign in to comment.