diff --git a/README.md b/README.md
index aac07a5f0f..52ab78c6df 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+
[**Installing**](https://github.com/rubberduck-vba/Rubberduck/wiki/Installing) • [Contributing](https://github.com/rubberduck-vba/Rubberduck/blob/next/CONTRIBUTING.md) • [Attributions](https://github.com/rubberduck-vba/Rubberduck/blob/next/docs/Attributions.md) • [Blog](https://rubberduckvba.blog) • [Wiki](https://github.com/rubberduck-vba/Rubberduck/wiki) • [rubberduckvba.com](https://rubberduckvba.com)
diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/EmptyMethodInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/EmptyMethodInspection.cs
index 4d8f0d4bbe..baaf3d6688 100644
--- a/Rubberduck.CodeAnalysis/Inspections/Concrete/EmptyMethodInspection.cs
+++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/EmptyMethodInspection.cs
@@ -33,7 +33,7 @@ namespace Rubberduck.CodeAnalysis.Inspections.Concrete
/// ]]>
///
///
- internal class EmptyMethodInspection : DeclarationInspectionBase
+ internal sealed class EmptyMethodInspection : DeclarationInspectionBase
{
public EmptyMethodInspection(IDeclarationFinderProvider declarationFinderProvider)
: base(declarationFinderProvider, DeclarationType.Member)
diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/EmptyModuleInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/EmptyModuleInspection.cs
index 1350acff4b..fc99597114 100644
--- a/Rubberduck.CodeAnalysis/Inspections/Concrete/EmptyModuleInspection.cs
+++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/EmptyModuleInspection.cs
@@ -84,7 +84,7 @@ public override bool VisitModuleDeclarations(VBAParser.ModuleDeclarationsContext
public override bool VisitModuleDeclarationsElement(VBAParser.ModuleDeclarationsElementContext context)
{
return context.moduleVariableStmt() == null
- && context.constStmt() == null
+ && context.moduleConstStmt() == null
&& context.enumerationStmt() == null
&& context.udtDeclaration() == null
&& context.eventStmt() == null
diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureCanBeWrittenAsFunctionInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureCanBeWrittenAsFunctionInspection.cs
index fcb4f63c7b..24ebd6735d 100644
--- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureCanBeWrittenAsFunctionInspection.cs
+++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ProcedureCanBeWrittenAsFunctionInspection.cs
@@ -13,8 +13,8 @@ namespace Rubberduck.CodeAnalysis.Inspections.Concrete
/// Warns about 'Sub' procedures that could be refactored into a 'Function'.
///
///
- /// Idiomatic VB code uses 'Function' procedures to return a single value. If the procedure isn't side-effecting, consider writing is as a
- /// 'Function' rather than a 'Sub' the returns a result through a 'ByRef' parameter.
+ /// Idiomatic VB code uses 'Function' procedures to return a single value. If the procedure isn't side-effecting, consider writing it as a
+ /// 'Function' rather than a 'Sub' that returns a result through a 'ByRef' parameter.
///
///
///
diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/PublicEnumerationDeclaredInWorksheetInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/PublicEnumerationDeclaredInWorksheetInspection.cs
new file mode 100644
index 0000000000..ac5bbe60e4
--- /dev/null
+++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/PublicEnumerationDeclaredInWorksheetInspection.cs
@@ -0,0 +1,83 @@
+using Rubberduck.CodeAnalysis.Inspections.Abstract;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using Rubberduck.Parsing.VBA.DeclarationCaching;
+using Rubberduck.Resources.Inspections;
+using Rubberduck.VBEditor.SafeComWrappers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.CodeAnalysis.Inspections.Concrete
+{
+ ///
+ /// Identifies public enumerations declared within worksheet modules.
+ ///
+ ///
+ /// Copying a worksheet which contains a public Enum declaration will also create a copy of the Enum declaration.
+ /// The copied Enum declaration will result in an 'Ambiguous name detected' compiler error.
+ /// Declaring Enumerations in Standard or Class modules avoids unintentional duplication of an Enum declaration.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal sealed class PublicEnumerationDeclaredInWorksheetInspection : DeclarationInspectionBase
+ {
+ private readonly string[] _worksheetSuperTypeNames = new string[] { "Worksheet", "_Worksheet" };
+
+ public PublicEnumerationDeclaredInWorksheetInspection(IDeclarationFinderProvider declarationFinderProvider)
+ : base(declarationFinderProvider, DeclarationType.Enumeration)
+ {}
+
+ protected override bool IsResultDeclaration(Declaration enumeration, DeclarationFinder finder)
+ {
+ if (enumeration.Accessibility != Accessibility.Private
+ && enumeration.QualifiedModuleName.ComponentType == ComponentType.Document)
+ {
+ if (enumeration.ParentDeclaration is ClassModuleDeclaration classModuleDeclaration)
+ {
+ return RetrieveSuperTypeNames(classModuleDeclaration).Intersect(_worksheetSuperTypeNames).Any();
+ }
+ }
+
+ return false;
+ }
+
+ protected override string ResultDescription(Declaration declaration)
+ {
+ return string.Format(InspectionResults.PublicEnumerationDeclaredInWorksheetInspection,
+ declaration.IdentifierName);
+ }
+
+ ///
+ /// Supports property injection for testing.
+ ///
+ ///
+ /// MockParser does not populate SuperTypes/SuperTypeNames. RetrieveSuperTypeNames Func allows injection
+ /// of ClassModuleDeclaration.SuperTypeNames property results.
+ ///
+ public Func> RetrieveSuperTypeNames { set; private get; } = GetSuperTypeNames;
+
+ private static IEnumerable GetSuperTypeNames(ClassModuleDeclaration classModule)
+ {
+ return classModule.SupertypeNames;
+ }
+ }
+}
diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/SheetAccessedUsingStringInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/SheetAccessedUsingStringInspection.cs
index e4db4f955b..a1e98f2cfa 100644
--- a/Rubberduck.CodeAnalysis/Inspections/Concrete/SheetAccessedUsingStringInspection.cs
+++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/SheetAccessedUsingStringInspection.cs
@@ -229,9 +229,6 @@ private static string ComponentPropertyValue(IVBComponent component, string prop
return null;
}
- protected override string ResultDescription(IdentifierReference reference, string codeName)
- {
- return InspectionResults.SheetAccessedUsingStringInspection;
- }
+ protected override string ResultDescription(IdentifierReference reference, string codeName) => InspectionResults.SheetAccessedUsingStringInspection;
}
}
diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/KeywordsUsedAsMemberInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/KeywordsUsedAsMemberInspection.cs
index 6a5a946820..b80fb257e8 100644
--- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/KeywordsUsedAsMemberInspection.cs
+++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/KeywordsUsedAsMemberInspection.cs
@@ -19,7 +19,7 @@ namespace Rubberduck.CodeAnalysis.Inspections.Concrete.ThunderCode
/// While perfectly legal as Type or Enum member names, these identifiers should be avoided:
/// they need to be square-bracketed everywhere they are used.
///
- internal class KeywordsUsedAsMemberInspection : DeclarationInspectionBase
+ internal sealed class KeywordsUsedAsMemberInspection : DeclarationInspectionBase
{
public KeywordsUsedAsMemberInspection(IDeclarationFinderProvider declarationFinderProvider)
: base(declarationFinderProvider, DeclarationType.EnumerationMember, DeclarationType.UserDefinedTypeMember)
diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NonBreakingSpaceIdentifierInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NonBreakingSpaceIdentifierInspection.cs
index aa9e8bf798..4987df727c 100644
--- a/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NonBreakingSpaceIdentifierInspection.cs
+++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/ThunderCode/NonBreakingSpaceIdentifierInspection.cs
@@ -14,7 +14,7 @@ namespace Rubberduck.CodeAnalysis.Inspections.Concrete.ThunderCode
/// code our friend Andrew Jackson would have written to confuse Rubberduck's parser and/or resolver.
/// This inspection may accidentally reveal non-breaking spaces in code copied and pasted from a website.
///
- internal class NonBreakingSpaceIdentifierInspection : DeclarationInspectionBase
+ internal sealed class NonBreakingSpaceIdentifierInspection : DeclarationInspectionBase
{
private const string Nbsp = "\u00A0";
diff --git a/Rubberduck.CodeAnalysis/Inspections/Concrete/UDTMemberNotUsedInspection.cs b/Rubberduck.CodeAnalysis/Inspections/Concrete/UDTMemberNotUsedInspection.cs
new file mode 100644
index 0000000000..9857359cdd
--- /dev/null
+++ b/Rubberduck.CodeAnalysis/Inspections/Concrete/UDTMemberNotUsedInspection.cs
@@ -0,0 +1,83 @@
+using Rubberduck.CodeAnalysis.Inspections.Abstract;
+using Rubberduck.CodeAnalysis.Inspections.Extensions;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using Rubberduck.Parsing.VBA.DeclarationCaching;
+using Rubberduck.Resources.Inspections;
+using System.Linq;
+
+namespace Rubberduck.CodeAnalysis.Inspections.Concrete
+{
+ ///
+ /// Warns about User Defined Type (UDT) members that are never referenced.
+ ///
+ ///
+ /// Declarations that are never used should be removed.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal sealed class UDTMemberNotUsedInspection : DeclarationInspectionBase
+ {
+ public UDTMemberNotUsedInspection(IDeclarationFinderProvider declarationFinderProvider)
+ : base(declarationFinderProvider, DeclarationType.UserDefinedTypeMember)
+ {}
+
+ protected override bool IsResultDeclaration(Declaration declaration, DeclarationFinder finder)
+ {
+ return declaration.DeclarationType.Equals(DeclarationType.UserDefinedTypeMember)
+ && !declaration.References.Any();
+ }
+
+ protected override string ResultDescription(Declaration declaration)
+ {
+ var declarationType = declaration.DeclarationType.ToLocalizedString();
+ var declarationName = declaration.IdentifierName;
+ return string.Format(
+ InspectionResults.IdentifierNotUsedInspection,
+ declarationType,
+ declarationName);
+ }
+ }
+}
diff --git a/Rubberduck.CodeAnalysis/QuickFixes/Abstract/QuickFixBase.cs b/Rubberduck.CodeAnalysis/QuickFixes/Abstract/QuickFixBase.cs
index 10545fb184..851cc390bc 100644
--- a/Rubberduck.CodeAnalysis/QuickFixes/Abstract/QuickFixBase.cs
+++ b/Rubberduck.CodeAnalysis/QuickFixes/Abstract/QuickFixBase.cs
@@ -55,6 +55,22 @@ public void RemoveInspections(params Type[] inspections)
public virtual CodeKind TargetCodeKind => CodeKind.CodePaneCode;
public abstract void Fix(IInspectionResult result, IRewriteSession rewriteSession);
+
+ ///
+ /// FixMany defers the enumeration of inspection results to the QuickFix
+ ///
+ ///
+ /// The default implementation enumerates the results collection calling Fix() for each result.
+ /// Override this funcion when a QuickFix needs operate on results as a group (e.g., RemoveUnusedDeclarationQuickFix)
+ ///
+ public virtual void Fix(IReadOnlyCollection results, IRewriteSession rewriteSession)
+ {
+ foreach (var result in results)
+ {
+ Fix(result, rewriteSession);
+ }
+ }
+
public abstract string Description(IInspectionResult result);
public abstract bool CanFixMultiple { get; }
diff --git a/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveOptionBaseStatementQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveRedundantOptionStatementQuickFix.cs
similarity index 85%
rename from Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveOptionBaseStatementQuickFix.cs
rename to Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveRedundantOptionStatementQuickFix.cs
index 2538a3669f..c153475574 100644
--- a/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveOptionBaseStatementQuickFix.cs
+++ b/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveRedundantOptionStatementQuickFix.cs
@@ -35,9 +35,9 @@ namespace Rubberduck.CodeAnalysis.QuickFixes.Concrete
/// ]]>
///
///
- internal sealed class RemoveOptionBaseStatementQuickFix : QuickFixBase
+ internal sealed class RemoveRedundantOptionStatementQuickFix : QuickFixBase
{
- public RemoveOptionBaseStatementQuickFix()
+ public RemoveRedundantOptionStatementQuickFix()
: base(typeof(RedundantOptionInspection))
{}
@@ -47,7 +47,12 @@ public override void Fix(IInspectionResult result, IRewriteSession rewriteSessio
rewriter.Remove(result.Context);
}
- public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.RemoveOptionBaseStatementQuickFix;
+ public override string Description(IInspectionResult result)
+ {
+ return string.Format(
+ Resources.Inspections.QuickFixes.RemoveRedundantOptionStatementQuickFix,
+ result.Context.GetText());
+ }
public override bool CanFixMultiple => true;
public override bool CanFixInProcedure => false;
diff --git a/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveUnassignedIdentifierQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveUnassignedIdentifierQuickFix.cs
index 99332eca24..ac24979f85 100644
--- a/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveUnassignedIdentifierQuickFix.cs
+++ b/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveUnassignedIdentifierQuickFix.cs
@@ -2,6 +2,10 @@
using Rubberduck.CodeAnalysis.Inspections.Concrete;
using Rubberduck.CodeAnalysis.QuickFixes.Abstract;
using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Refactorings;
+using Rubberduck.Refactorings.DeleteDeclarations;
+using System.Collections.Generic;
+using System.Linq;
namespace Rubberduck.CodeAnalysis.QuickFixes.Concrete
{
@@ -35,14 +39,25 @@ namespace Rubberduck.CodeAnalysis.QuickFixes.Concrete
///
internal sealed class RemoveUnassignedIdentifierQuickFix : QuickFixBase
{
- public RemoveUnassignedIdentifierQuickFix()
+ private readonly ICodeOnlyRefactoringAction _refactoring;
+ public RemoveUnassignedIdentifierQuickFix(DeleteDeclarationsRefactoringAction refactoringAction)
: base(typeof(VariableNotAssignedInspection))
- {}
+ {
+ _refactoring = refactoringAction;
+ }
public override void Fix(IInspectionResult result, IRewriteSession rewriteSession)
{
- var rewriter = rewriteSession.CheckOutModuleRewriter(result.Target.QualifiedModuleName);
- rewriter.Remove(result.Target);
+ var model = new DeleteDeclarationsModel(result.Target);
+
+ _refactoring.Refactor(model, rewriteSession);
+ }
+
+ public override void Fix(IReadOnlyCollection results, IRewriteSession rewriteSession)
+ {
+ var model = new DeleteDeclarationsModel(results.Select(r => r.Target));
+
+ _refactoring.Refactor(model, rewriteSession);
}
public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.RemoveUnassignedIdentifierQuickFix;
diff --git a/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveUnusedDeclarationQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveUnusedDeclarationQuickFix.cs
index 2116c8d0ac..8bd00953db 100644
--- a/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveUnusedDeclarationQuickFix.cs
+++ b/Rubberduck.CodeAnalysis/QuickFixes/Concrete/RemoveUnusedDeclarationQuickFix.cs
@@ -2,6 +2,12 @@
using Rubberduck.CodeAnalysis.Inspections.Concrete;
using Rubberduck.CodeAnalysis.QuickFixes.Abstract;
using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Refactorings;
+using Rubberduck.Refactorings.Common;
+using Rubberduck.Refactorings.DeleteDeclarations;
+using System.Collections.Generic;
+using System.Linq;
namespace Rubberduck.CodeAnalysis.QuickFixes.Concrete
{
@@ -31,7 +37,6 @@ namespace Rubberduck.CodeAnalysis.QuickFixes.Concrete
/// Option Explicit
///
/// Public Sub DoSomething()
- ///
/// Debug.Print 42
/// End Sub
/// ]]>
@@ -39,17 +44,30 @@ namespace Rubberduck.CodeAnalysis.QuickFixes.Concrete
///
internal sealed class RemoveUnusedDeclarationQuickFix : QuickFixBase
{
- public RemoveUnusedDeclarationQuickFix()
+ private readonly ICodeOnlyRefactoringAction _refactoring;
+
+ public RemoveUnusedDeclarationQuickFix(DeleteDeclarationsRefactoringAction refactoringAction)
: base(typeof(ConstantNotUsedInspection),
typeof(ProcedureNotUsedInspection),
typeof(VariableNotUsedInspection),
- typeof(LineLabelNotUsedInspection))
- {}
+ typeof(LineLabelNotUsedInspection),
+ typeof(UDTMemberNotUsedInspection))
+ {
+ _refactoring = refactoringAction;
+ }
public override void Fix(IInspectionResult result, IRewriteSession rewriteSession)
{
- var rewriter = rewriteSession.CheckOutModuleRewriter(result.Target.QualifiedModuleName);
- rewriter.Remove(result.Target);
+ var model = new DeleteDeclarationsModel(result.Target);
+
+ _refactoring.Refactor(model, rewriteSession);
+ }
+
+ public override void Fix(IReadOnlyCollection results, IRewriteSession rewriteSession)
+ {
+ var model = new DeleteDeclarationsModel(results.Select(r => r.Target));
+
+ _refactoring.Refactor(model, rewriteSession);
}
public override string Description(IInspectionResult result) => Resources.Inspections.QuickFixes.RemoveUnusedDeclarationQuickFix;
diff --git a/Rubberduck.CodeAnalysis/QuickFixes/IQuickFix.cs b/Rubberduck.CodeAnalysis/QuickFixes/IQuickFix.cs
index f533cf473d..3de80809f8 100644
--- a/Rubberduck.CodeAnalysis/QuickFixes/IQuickFix.cs
+++ b/Rubberduck.CodeAnalysis/QuickFixes/IQuickFix.cs
@@ -9,6 +9,7 @@ namespace Rubberduck.CodeAnalysis.QuickFixes
public interface IQuickFix
{
void Fix(IInspectionResult result, IRewriteSession rewriteSession);
+ void Fix(IReadOnlyCollection results, IRewriteSession rewriteSession);
string Description(IInspectionResult result);
bool CanFixMultiple { get; }
diff --git a/Rubberduck.CodeAnalysis/QuickFixes/Logistics/QuickFixProvider.cs b/Rubberduck.CodeAnalysis/QuickFixes/Logistics/QuickFixProvider.cs
index 308a1bbf9f..48835f7c3c 100644
--- a/Rubberduck.CodeAnalysis/QuickFixes/Logistics/QuickFixProvider.cs
+++ b/Rubberduck.CodeAnalysis/QuickFixes/Logistics/QuickFixProvider.cs
@@ -59,17 +59,17 @@ public bool CanFix(IQuickFix fix, IInspectionResult result)
&& !result.DisabledQuickFixes.Contains(fix.GetType().Name);
}
- public void Fix(IQuickFix fix, IInspectionResult result)
+ public void Fix(IQuickFix quickFix, IInspectionResult result)
{
- if (!CanFix(fix, result))
+ if (!CanFix(quickFix, result))
{
return;
}
- var rewriteSession = RewriteSession(fix.TargetCodeKind);
+ var rewriteSession = RewriteSession(quickFix.TargetCodeKind);
try
{
- fix.Fix(result, rewriteSession);
+ quickFix.Fix(result, rewriteSession);
}
catch (RewriteFailedException)
{
@@ -78,24 +78,24 @@ public void Fix(IQuickFix fix, IInspectionResult result)
Apply(rewriteSession);
}
- public void Fix(IQuickFix fix, IEnumerable resultsToFix)
+ public void Fix(IQuickFix quickFix, IEnumerable resultsToFix)
{
- var results = resultsToFix.ToList();
+ var fixableResults = resultsToFix.Where(r => CanFix(quickFix, r)).ToList();
- if (!results.Any())
+ if (!fixableResults.Any())
{
return;
}
- var rewriteSession = RewriteSession(fix.TargetCodeKind);
- foreach (var result in results)
- {
- if (!CanFix(fix, result))
- {
- continue;
- }
+ var rewriteSession = RewriteSession(quickFix.TargetCodeKind);
- fix.Fix(result, rewriteSession);
+ try
+ {
+ quickFix.Fix(fixableResults, rewriteSession);
+ }
+ catch (RewriteFailedException)
+ {
+ _failureNotifier.NotifyQuickFixExecutionFailure(rewriteSession.Status);
}
Apply(rewriteSession);
}
diff --git a/Rubberduck.Core/UI/AddRemoveReferences/AddRemoveReferencesUI.it.resx b/Rubberduck.Core/UI/AddRemoveReferences/AddRemoveReferencesUI.it.resx
index 5d63958833..6972611447 100644
--- a/Rubberduck.Core/UI/AddRemoveReferences/AddRemoveReferencesUI.it.resx
+++ b/Rubberduck.Core/UI/AddRemoveReferences/AddRemoveReferencesUI.it.resx
@@ -131,6 +131,7 @@
Database di Microsoft Access({0})|{0}
+ {0} = elenco delle estensioni delimitato da punto e virgola nel formato *.ext
Controlli ActiveX (*.ocx)|*.ocx
@@ -140,33 +141,40 @@
File di Microsoft Excel ({0})|{0}
+ {0} = elenco delle estensioni delimitato da punto e virgola nel formato *.ext
File eseguibili (*.exe;*.dll)|*.exe;*.dll
File VBA di Outlook ({0})|{0}
+ {0} = elenco delle estensioni delimitato da punto e virgola nel formato *.ext
File Addin di PowerPoint({0})|{0}
+ {0} = elenco delle estensioni delimitato da punto e virgola nel formato *.ext
File di Publisher ({0}|{0}
+ {0} = elenco delle estensioni delimitato da punto e virgola nel formato *.ext
Tipi Libreria (*.olb;*.tlb;*.dll)|*.olb;*.tlb;*.dll
Tutti i file di Visio ({0})|{0}
+ {0} = elenco delle estensioni delimitato da punto e virgola nel formato *.ext
Documenti di Word ({0})|{0}
+ {0} = elenco delle estensioni delimitato da punto e virgola nel formato *.ext
Aggiungi/Rimuovi Riferimento...
Aggiungi/Rimuovi Riferimenti - {0}
+ {0} = Nome del progetto
Standard
diff --git a/Rubberduck.Core/UI/Command/ComCommands/ExportAllCommand.cs b/Rubberduck.Core/UI/Command/ComCommands/ExportAllCommand.cs
index 6d0e2adc4d..01ba967f84 100644
--- a/Rubberduck.Core/UI/Command/ComCommands/ExportAllCommand.cs
+++ b/Rubberduck.Core/UI/Command/ComCommands/ExportAllCommand.cs
@@ -1,4 +1,5 @@
using Path = System.IO.Path;
+using Directory = System.IO.Directory;
using System.Windows.Forms;
using Rubberduck.Navigation.CodeExplorer;
using Rubberduck.Resources;
@@ -6,25 +7,29 @@
using Rubberduck.VBEditor.Events;
using Rubberduck.VBEditor.SafeComWrappers;
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
+using System.Collections.Generic;
namespace Rubberduck.UI.Command.ComCommands
{
public class ExportAllCommand : ComCommandBase
{
private readonly IVBE _vbe;
- private readonly IFileSystemBrowserFactory _factory;
private readonly IProjectsProvider _projectsProvider;
+ private readonly IFileSystemBrowserFactory _factory;
+ private readonly ProjectToExportFolderMap _projectToExportFolderMap;
public ExportAllCommand(
IVBE vbe,
IFileSystemBrowserFactory folderBrowserFactory,
IVbeEvents vbeEvents,
- IProjectsProvider projectsProvider)
+ IProjectsProvider projectsProvider,
+ ProjectToExportFolderMap projectToExportFolderMap)
: base(vbeEvents)
{
_vbe = vbe;
_factory = folderBrowserFactory;
_projectsProvider = projectsProvider;
+ _projectToExportFolderMap = projectToExportFolderMap;
AddToCanExecuteEvaluation(SpecialEvaluateCanExecute);
}
@@ -103,23 +108,62 @@ protected override void OnExecute(object parameter)
private void Export(IVBProject project)
{
- var desc = string.Format(RubberduckUI.ExportAllCommand_SaveAsDialog_Title, project.Name);
+ var initialFolderBrowserPath = GetInitialFolderBrowserPath(project);
- // If .GetDirectoryName is passed an empty string for a RootFolder,
- // it defaults to the Documents library (Win 7+) or equivalent.
- var path = string.IsNullOrWhiteSpace(project.FileName)
- ? string.Empty
- : Path.GetDirectoryName(project.FileName);
+ var desc = string.Format(RubberduckUI.ExportAllCommand_SaveAsDialog_Title, project.Name);
- using (var _folderBrowser = _factory.CreateFolderBrowser(desc, true, path))
+ using (var _folderBrowser = _factory.CreateFolderBrowser(desc, true, initialFolderBrowserPath))
{
var result = _folderBrowser.ShowDialog();
if (result == DialogResult.OK)
{
+ _projectToExportFolderMap.AssignProjectExportFolder(project, _folderBrowser.SelectedPath);
project.ExportSourceFiles(_folderBrowser.SelectedPath);
}
}
}
+
+ //protected scope to support testing
+ protected string GetInitialFolderBrowserPath(IVBProject project)
+ {
+ if (_projectToExportFolderMap.TryGetExportPathForProject(project, out string initialFolderBrowserPath))
+ {
+ if (FolderExists(initialFolderBrowserPath))
+ {
+ //Return the cached folderpath of the previous ExportAllCommand process
+ return initialFolderBrowserPath;
+ }
+
+ //The folder used in the previous ExportAllComand process no longer exists, remove the cached folderpath
+ _projectToExportFolderMap.RemoveProject(project);
+ }
+
+ //The folder of the workbook, or an empty string
+ initialFolderBrowserPath = GetDefaultExportFolder(project.FileName);
+
+ if (!string.IsNullOrEmpty(initialFolderBrowserPath))
+ {
+ _projectToExportFolderMap.AssignProjectExportFolder(project, initialFolderBrowserPath);
+ }
+
+ return initialFolderBrowserPath;
+ }
+
+ //protected scope to support testing
+ protected string GetDefaultExportFolder(string projectFileName)
+ {
+ // If .GetDirectoryName is passed an empty string for a RootFolder,
+ // it defaults to the Documents library (Win 7+) or equivalent.
+ return string.IsNullOrWhiteSpace(projectFileName)
+ ? string.Empty
+ : Path.GetDirectoryName(projectFileName);
+ }
+
+ //protected virtual to support testing
+ protected virtual bool FolderExists(string path)
+ {
+ return Directory.Exists(path);
+ }
}
}
\ No newline at end of file
diff --git a/Rubberduck.Core/UI/ProjectToExportFolderMap.cs b/Rubberduck.Core/UI/ProjectToExportFolderMap.cs
new file mode 100644
index 0000000000..60fc5c808b
--- /dev/null
+++ b/Rubberduck.Core/UI/ProjectToExportFolderMap.cs
@@ -0,0 +1,51 @@
+using Rubberduck.VBEditor.SafeComWrappers.Abstract;
+using System.Collections.Generic;
+
+namespace Rubberduck.UI
+{
+ ///
+ /// ProjectToExportFolderMap is a singleton container of Project/Folder pairs to
+ /// support multiple instances of the ExportAllCommand class
+ ///
+ public class ProjectToExportFolderMap
+ {
+ private readonly Dictionary _projectToExportFolderMap;
+
+ public ProjectToExportFolderMap()
+ {
+ _projectToExportFolderMap = new Dictionary();
+ }
+
+ public void AssignProjectExportFolder(IVBProject project, string exportFolderpath)
+ {
+ if (project is null || string.IsNullOrWhiteSpace(exportFolderpath))
+ {
+ return;
+ }
+
+ if (!_projectToExportFolderMap.ContainsKey(project.FileName))
+ {
+ _projectToExportFolderMap.Add(project.FileName, exportFolderpath);
+ return;
+ }
+
+ _projectToExportFolderMap[project.FileName] = exportFolderpath;
+ }
+
+ public bool TryGetExportPathForProject(IVBProject project, out string exportFolderpath)
+ {
+ exportFolderpath = string.Empty;
+ if (string.IsNullOrWhiteSpace(project.FileName))
+ {
+ return false;
+ }
+
+ return _projectToExportFolderMap.TryGetValue(project.FileName, out exportFolderpath);
+ }
+
+ public void RemoveProject(IVBProject project)
+ {
+ _projectToExportFolderMap.Remove(project.FileName);
+ }
+ }
+}
diff --git a/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsagePresenter.cs b/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsagePresenter.cs
new file mode 100644
index 0000000000..cebcd0f43c
--- /dev/null
+++ b/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsagePresenter.cs
@@ -0,0 +1,34 @@
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Refactorings;
+using Rubberduck.Refactorings.MoveCloserToUsage;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rubberduck.UI.Refactorings.MoveCloserToUsage
+{
+ class MoveCloserToUsagePresenter : RefactoringPresenterBase, IMoveCloserToUsagePresenter
+ {
+
+ private static readonly DialogData DialogData = DialogData.Create("RefactoringsUI.MoveCloserToUsageDialog_Caption", 164, 684);
+
+ public MoveCloserToUsagePresenter(MoveCloserToUsageModel model, IRefactoringDialogFactory dialogFactory) :
+ base(DialogData, model, dialogFactory)
+ {
+ }
+
+ public MoveCloserToUsageModel Show(VariableDeclaration target)
+ {
+ if (null == target)
+ {
+ return null;
+ }
+
+ Model.Target = target;
+
+ return Show();
+ }
+ }
+}
diff --git a/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageView.xaml b/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageView.xaml
new file mode 100644
index 0000000000..f3c247d86b
--- /dev/null
+++ b/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageView.xaml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageView.xaml.cs b/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageView.xaml.cs
new file mode 100644
index 0000000000..f897818bf1
--- /dev/null
+++ b/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageView.xaml.cs
@@ -0,0 +1,31 @@
+using Rubberduck.Refactorings;
+using Rubberduck.Refactorings.MoveCloserToUsage;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Rubberduck.UI.Refactorings.MoveCloserToUsage
+{
+ ///
+ /// Interaktionslogik für MoveCloserToUsageView.xaml
+ ///
+ public partial class MoveCloserToUsageView : IRefactoringView
+ {
+ public MoveCloserToUsageView()
+ {
+ InitializeComponent();
+ }
+
+ }
+}
diff --git a/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageViewModel.cs b/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageViewModel.cs
new file mode 100644
index 0000000000..52cbd62659
--- /dev/null
+++ b/Rubberduck.Core/UI/Refactorings/MoveCloserToUsage/MoveCloserToUsageViewModel.cs
@@ -0,0 +1,48 @@
+using NLog;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Refactorings;
+using Rubberduck.Refactorings.MoveCloserToUsage;
+using Rubberduck.UI.Command;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rubberduck.UI.Refactorings.MoveCloserToUsage
+{
+ class MoveCloserToUsageViewModel : RefactoringViewModelBase
+ {
+ public MoveCloserToUsageViewModel(MoveCloserToUsageModel model) : base(model)
+ {
+ SetNewDeclarationStatementCommand = new DelegateCommand(LogManager.GetCurrentClassLogger(), (o) => SetNewDeclarationStatementExecute(o));
+ }
+
+ public Declaration Target => Model.Target;
+
+ public string Instructions
+ {
+ get
+ {
+ if (Target == null)
+ {
+ return RefactoringsUI.MoveCloserToUsageDialog_InstructionsLabelText;
+ }
+
+ return string.Format(RefactoringsUI.MoveCloserToUsageDialog_InstructionsLabelText, Target.IdentifierName);
+ }
+ }
+
+ public DelegateCommand SetNewDeclarationStatementCommand { get; }
+
+ void SetNewDeclarationStatementExecute(object param)
+ {
+ if (param is string newDeclarationStatement)
+ {
+ Model.DeclarationStatement = newDeclarationStatement;
+ }
+
+ }
+
+ }
+}
diff --git a/Rubberduck.Core/UI/Settings/IndenterSettingsViewModel.cs b/Rubberduck.Core/UI/Settings/IndenterSettingsViewModel.cs
index ac4467a30b..c189a8dfdf 100644
--- a/Rubberduck.Core/UI/Settings/IndenterSettingsViewModel.cs
+++ b/Rubberduck.Core/UI/Settings/IndenterSettingsViewModel.cs
@@ -38,6 +38,7 @@ public IndenterSettingsViewModel(Configuration config, IConfigurationService ExportSettings(GetCurrentSettings()));
diff --git a/Rubberduck.Core/UI/Settings/InspectionSettings.xaml b/Rubberduck.Core/UI/Settings/InspectionSettings.xaml
index 54e0f40337..e4147eed24 100644
--- a/Rubberduck.Core/UI/Settings/InspectionSettings.xaml
+++ b/Rubberduck.Core/UI/Settings/InspectionSettings.xaml
@@ -65,7 +65,7 @@
Content="{Resx ResxName=Rubberduck.CodeAnalysis.CodeAnalysisUI, Key=CodeInspectionSettingsPage_FilterByDescription}" />
+ Text="{Binding InspectionSettingsDescriptionFilter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Delay=400}" Height="26" />
diff --git a/Rubberduck.Core/UI/ToDoItems/ToDoExplorerViewModel.cs b/Rubberduck.Core/UI/ToDoItems/ToDoExplorerViewModel.cs
index 7675e88e6a..6e328eae70 100644
--- a/Rubberduck.Core/UI/ToDoItems/ToDoExplorerViewModel.cs
+++ b/Rubberduck.Core/UI/ToDoItems/ToDoExplorerViewModel.cs
@@ -294,7 +294,7 @@ private IEnumerable GetToDoMarkers(CommentNode comment)
{
var markers = _configService.Read().UserSettings.ToDoListSettings.ToDoMarkers;
return markers.Where(marker => !string.IsNullOrEmpty(marker.Text)
- && Regex.IsMatch(comment.CommentText, @"\b" + Regex.Escape(marker.Text) + @"\b", RegexOptions.IgnoreCase))
+ && Regex.IsMatch(comment.CommentText, @"^\s*" + Regex.Escape(marker.Text) + @"\b", RegexOptions.IgnoreCase))
.Select(marker => new ToDoItem(marker.Text, comment));
}
diff --git a/Rubberduck.Main/Root/RubberduckIoCInstaller.cs b/Rubberduck.Main/Root/RubberduckIoCInstaller.cs
index 253f84e749..3102f3c3ca 100644
--- a/Rubberduck.Main/Root/RubberduckIoCInstaller.cs
+++ b/Rubberduck.Main/Root/RubberduckIoCInstaller.cs
@@ -174,6 +174,9 @@ public void Install(IWindsorContainer container, IConfigurationStore store)
RegisterDockablePresenters(container);
RegisterDockableUserControls(container);
+ container.Register(Component.For()
+ .LifestyleSingleton());
+
RegisterCommands(container);
RegisterCommandMenuItems(container);
RegisterParentMenus(container);
@@ -394,6 +397,8 @@ private void RegisterSpecialFactories(IWindsorContainer container)
RegisterUnreachableCaseFactories(container);
RegisterEncapsulateFieldRefactoringFactories(container);
+
+ RegisterDeleteDeclarationsRefactoringActionFactories(container);
}
private void RegisterUnreachableCaseFactories(IWindsorContainer container)
@@ -422,6 +427,22 @@ private void RegisterEncapsulateFieldRefactoringFactories(IWindsorContainer cont
.LifestyleSingleton());
}
+ private void RegisterDeleteDeclarationsRefactoringActionFactories(IWindsorContainer container)
+ {
+ container.Kernel.Register(Component.For()
+ .ImplementedBy());
+
+ container.Kernel.Register(
+ Component.For()
+ .ImplementedBy().LifestyleTransient(),
+ Component.For().AsFactory().LifestyleSingleton());
+
+ container.Kernel.Register(
+ Component.For()
+ .ImplementedBy().LifestyleTransient(),
+ Component.For().AsFactory().LifestyleSingleton());
+ }
+
private void RegisterQuickFixes(IWindsorContainer container, Assembly[] assembliesToRegister)
{
foreach (var assembly in assembliesToRegister)
diff --git a/Rubberduck.Parsing/Grammar/VBAParser.g4 b/Rubberduck.Parsing/Grammar/VBAParser.g4
index f0aa570dca..420a95676c 100644
--- a/Rubberduck.Parsing/Grammar/VBAParser.g4
+++ b/Rubberduck.Parsing/Grammar/VBAParser.g4
@@ -102,7 +102,7 @@ moduleDeclarationsElement :
| defDirective
| enumerationStmt
| eventStmt
- | constStmt
+ | moduleConstStmt
| implementsStmt
| moduleVariableStmt
| moduleOption
@@ -114,6 +114,11 @@ moduleVariableStmt :
(endOfLine attributeStmt)*
;
+moduleConstStmt :
+ constStmt
+ (endOfLine attributeStmt)*
+;
+
moduleBody :
whiteSpace?
((moduleBodyElement | attributeStmt) endOfStatement)*;
diff --git a/Rubberduck.Parsing/Rewriter/RewriterInfo/ConstantRewriterInfoFinder.cs b/Rubberduck.Parsing/Rewriter/RewriterInfo/ConstantRewriterInfoFinder.cs
index 2e6553549c..aeee862022 100644
--- a/Rubberduck.Parsing/Rewriter/RewriterInfo/ConstantRewriterInfoFinder.cs
+++ b/Rubberduck.Parsing/Rewriter/RewriterInfo/ConstantRewriterInfoFinder.cs
@@ -24,7 +24,7 @@ private static RewriterInfo GetRewriterInfo(VBAParser.ConstSubStmtContext target
var itemIndex = items.ToList().IndexOf(target);
var count = items.Length;
- var element = context.Parent as VBAParser.ModuleDeclarationsElementContext;
+ var element = context.Parent?.Parent as VBAParser.ModuleDeclarationsElementContext;
if (element != null)
{
return GetModuleConstantRemovalInfo(target, element, count, itemIndex, items);
@@ -34,8 +34,11 @@ private static RewriterInfo GetRewriterInfo(VBAParser.ConstSubStmtContext target
}
private static RewriterInfo GetModuleConstantRemovalInfo(
- VBAParser.ConstSubStmtContext target, VBAParser.ModuleDeclarationsElementContext element,
- int count, int itemIndex, IReadOnlyList items)
+ VBAParser.ConstSubStmtContext target,
+ VBAParser.ModuleDeclarationsElementContext element,
+ int count,
+ int itemIndex,
+ IReadOnlyList items)
{
if (count == 1)
{
diff --git a/Rubberduck.Parsing/Symbols/Identifier.cs b/Rubberduck.Parsing/Symbols/Identifier.cs
index afecc45198..009424c300 100644
--- a/Rubberduck.Parsing/Symbols/Identifier.cs
+++ b/Rubberduck.Parsing/Symbols/Identifier.cs
@@ -47,6 +47,12 @@ public static string GetName(VBAParser.VariableSubStmtContext context)
return GetName(nameContext);
}
+ public static string GetName(VBAParser.ConstSubStmtContext context)
+ {
+ var nameContext = context.identifier();
+ return GetName(nameContext);
+ }
+
public static string GetName(VBAParser.ConstSubStmtContext context, out Interval tokenInterval)
{
var nameContext = context.identifier();
diff --git a/Rubberduck.Parsing/Symbols/ValuedDeclaration.cs b/Rubberduck.Parsing/Symbols/ValuedDeclaration.cs
index ebee682a3b..0583f97912 100644
--- a/Rubberduck.Parsing/Symbols/ValuedDeclaration.cs
+++ b/Rubberduck.Parsing/Symbols/ValuedDeclaration.cs
@@ -23,7 +23,10 @@ public ValuedDeclaration(
string value,
ParserRuleContext context,
Selection selection,
- bool isUserDefined = true)
+ bool isUserDefined = true,
+ ParserRuleContext attributesPassContext = null,
+ Attributes attributes = null
+ )
:base(
qualifiedName,
parentDeclaration,
@@ -35,12 +38,13 @@ public ValuedDeclaration(
accessibility,
declarationType,
context,
- null,
+ attributesPassContext,
selection,
false,
asTypeContext,
isUserDefined,
- annotations)
+ annotations,
+ attributes: attributes)
{
Expression = value;
}
diff --git a/Rubberduck.Parsing/VBA/DeclarationResolving/DeclarationSymbolsListener.cs b/Rubberduck.Parsing/VBA/DeclarationResolving/DeclarationSymbolsListener.cs
index eee4ff1ca5..eb5cfad8ca 100644
--- a/Rubberduck.Parsing/VBA/DeclarationResolving/DeclarationSymbolsListener.cs
+++ b/Rubberduck.Parsing/VBA/DeclarationResolving/DeclarationSymbolsListener.cs
@@ -721,6 +721,10 @@ public override void EnterConstSubStmt(VBAParser.ConstSubStmtContext context)
var value = context.expression().GetText();
var constStmt = (VBAParser.ConstStmtContext)context.Parent;
+ var key = (name, DeclarationType.Constant);
+ _attributes.TryGetValue(key, out var attributes);
+ _membersAllowingAttributes.TryGetValue(key, out var attributesPassContext);
+
var declaration = new ValuedDeclaration(
new QualifiedMemberName(_qualifiedModuleName, name),
_parentDeclaration,
@@ -733,7 +737,9 @@ public override void EnterConstSubStmt(VBAParser.ConstSubStmtContext context)
DeclarationType.Constant,
value,
context,
- identifier.GetSelection());
+ identifier.GetSelection(),
+ attributesPassContext: attributesPassContext,
+ attributes: attributes);
AddDeclaration(declaration);
}
diff --git a/Rubberduck.Parsing/VBA/Parsing/AttributeListener.cs b/Rubberduck.Parsing/VBA/Parsing/AttributeListener.cs
index d5931c9608..1d71d307e9 100644
--- a/Rubberduck.Parsing/VBA/Parsing/AttributeListener.cs
+++ b/Rubberduck.Parsing/VBA/Parsing/AttributeListener.cs
@@ -34,14 +34,24 @@ public override void EnterStartRule(VBAParser.StartRuleContext context)
public override void EnterModuleVariableStmt(VBAParser.ModuleVariableStmtContext context)
{
- var variableDeclarationStatemenList = context.variableStmt().variableListStmt().variableSubStmt();
- foreach (var variableContext in variableDeclarationStatemenList)
+ var variableDeclarationStatementList = context.variableStmt().variableListStmt().variableSubStmt();
+ foreach (var variableContext in variableDeclarationStatementList)
{
var variableName = Identifier.GetName(variableContext);
_membersAllowingAttributes[(variableName, DeclarationType.Variable)] = context;
}
}
+ public override void EnterModuleConstStmt(VBAParser.ModuleConstStmtContext context)
+ {
+ var constantDeclarationStatementList = context.constStmt().constSubStmt();
+ foreach (var constContext in constantDeclarationStatementList)
+ {
+ var constantName = Identifier.GetName(constContext);
+ _membersAllowingAttributes[(constantName, DeclarationType.Constant)] = context;
+ }
+ }
+
public override void EnterDeclareStmt(VBAParser.DeclareStmtContext context)
{
var name = Identifier.GetName(context.identifier());
@@ -159,19 +169,29 @@ public override void ExitAttributeStmt(VBAParser.AttributeStmtContext context)
var scopeName = attributeNameParts[0];
- //Might be an attribute for the enclosing procedure, function or poperty.
+ //Might be an attribute for the enclosing procedure, function or property.
if (_currentScopeAttributes != null && scopeName.Equals(_currentScope.scopeIdentifier, StringComparison.OrdinalIgnoreCase))
{
AddOrUpdateAttribute(_currentScopeAttributes, attributeName, context);
return;
}
+ //Member attributes
+ //Due to the VBA naming rules, a name can only refer to either a variable or constant.
+
//Member variable attributes
var moduleVariableScope = (scopeName, DeclarationType.Variable);
if (_membersAllowingAttributes.TryGetValue(moduleVariableScope, out _))
{
AddOrUpdateAttribute(moduleVariableScope, attributeName, context);
}
+
+ //Member constant attributes
+ var moduleConstantScope = (scopeName, DeclarationType.Constant);
+ if (_membersAllowingAttributes.TryGetValue(moduleConstantScope, out _))
+ {
+ AddOrUpdateAttribute(moduleConstantScope, attributeName, context);
+ }
}
private void AddOrUpdateAttribute((string scopeName, DeclarationType Variable) moduleVariableScope,
diff --git a/Rubberduck.Refactorings/Common/IModuleRewriterExtensions.cs b/Rubberduck.Refactorings/Common/IModuleRewriterExtensions.cs
deleted file mode 100644
index 8f037a19c7..0000000000
--- a/Rubberduck.Refactorings/Common/IModuleRewriterExtensions.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using Antlr4.Runtime;
-using Rubberduck.Parsing;
-using Rubberduck.Parsing.Grammar;
-using Rubberduck.Parsing.Rewriter;
-using Rubberduck.Parsing.Symbols;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Rubberduck.Refactorings.Common
-{
- public static class IModuleRewriterExtensions
- {
- ///
- /// Removes variable declaration and subsequent VBAParser.EndOfStatementContext
- /// depending on the flag.
- /// This function is intended to be called only once per rewriter within a given ModuleRewriteSession.
- ///
- ///
- /// Calling this function with defaulted to true
- /// avoids leaving residual newlines between the deleted declaration and the next declaration.
- /// The one-time call constraint is required for scenarios where variables to delete are declared in a list. Specifically,
- /// the use case where all the variables in the list are to be removed.
- /// If the variables to remove are not declared in a list, then this function can be called multiple times.
- ///
- public static void RemoveVariables(this IModuleRewriter rewriter, IEnumerable toRemove, bool removeEndOfStmtContext = true)
- {
- if (!toRemove.Any())
- {
- return;
- }
-
- var fieldsToDeleteByListContext = toRemove.Distinct()
- .ToLookup(f => f.Context.GetAncestor());
-
- foreach (var fieldsToDelete in fieldsToDeleteByListContext)
- {
- var variableList = fieldsToDelete.Key.children.OfType();
-
- if (variableList.Count() == fieldsToDelete.Count())
- {
- if (fieldsToDelete.First().ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Module))
- {
- rewriter.RemoveDeclarationContext(fieldsToDelete.First(), removeEndOfStmtContext);
- }
- else
- {
- rewriter.RemoveDeclarationContext(fieldsToDelete.First(), removeEndOfStmtContext);
- }
- continue;
- }
-
- foreach (var target in fieldsToDelete)
- {
- rewriter.Remove(target);
- }
- }
- }
-
- ///
- /// Removes a member declaration and subsequent VBAParser.EndOfStatementContext
- /// depending on the flag.
- ///
- ///
- /// Calling this function with defaulted to true
- /// avoids leaving residual newlines between the deleted declaration and the next declaration.
- ///
- public static void RemoveMember(this IModuleRewriter rewriter, ModuleBodyElementDeclaration target, bool removeEndOfStmtContext = true)
- {
- RemoveMembers(rewriter, new ModuleBodyElementDeclaration[] { target }, removeEndOfStmtContext);
- }
-
- ///
- /// Removes member declarations and subsequent VBAParser.EndOfStatementContext
- /// depending on the flag.
- ///
- ///
- /// Calling this function with defaulted to true
- /// avoids leaving residual newlines between the deleted declaration and the next declaration.
- ///
- public static void RemoveMembers(this IModuleRewriter rewriter, IEnumerable toRemove, bool removeEndOfStmtContext = true)
- {
- if (!toRemove.Any())
- {
- return;
- }
-
- foreach (var member in toRemove)
- {
- rewriter.RemoveDeclarationContext(member, removeEndOfStmtContext);
- }
- }
-
- private static void RemoveDeclarationContext(this IModuleRewriter rewriter, Declaration declaration, bool removeEndOfStmtContext = true) where T : ParserRuleContext
- {
- if (!declaration.Context.TryGetAncestor(out var elementContext))
- {
- throw new ArgumentException();
- }
-
- rewriter.Remove(elementContext);
- if (removeEndOfStmtContext && elementContext.TryGetFollowingContext(out var nextContext))
- {
- rewriter.Remove(nextContext);
- }
- }
- }
-}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/Abstract/DeleteElementsRefactoringActionBase.cs b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/DeleteElementsRefactoringActionBase.cs
new file mode 100644
index 0000000000..e69560a7c7
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/DeleteElementsRefactoringActionBase.cs
@@ -0,0 +1,353 @@
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations.Abstract
+{
+ public abstract class DeleteElementsRefactoringActionBase : CodeOnlyRefactoringActionBase where TModel : class, IRefactoringModel
+ {
+ private readonly IDeclarationFinderProvider _declarationFinderProvider;
+ private readonly IDeclarationDeletionTargetFactory _declarationDeletionTargetFactory;
+ private readonly IDeclarationDeletionGroupsGenerator _declarationDeletionGroupsGenerator;
+
+ private static readonly string _lineContinuationExpression = $"{Tokens.LineContinuation}{Environment.NewLine}";
+
+ protected const string EOS_COLON = ": ";
+
+ public DeleteElementsRefactoringActionBase(IDeclarationFinderProvider declarationFinderProvider,
+ IDeclarationDeletionTargetFactory deletionTargetFactory,
+ IDeclarationDeletionGroupsGeneratorFactory deletionGroupsGeneratorFactory,
+ IRewritingManager rewritingManager)
+ : base(rewritingManager)
+ {
+ _declarationFinderProvider = declarationFinderProvider;
+ _declarationDeletionTargetFactory = deletionTargetFactory;
+ _declarationDeletionGroupsGenerator = deletionGroupsGeneratorFactory.Create();
+ }
+
+ protected abstract bool CanRefactorAllTargets(TModel model);
+
+ protected void DeleteDeclarations(IDeleteDeclarationsModel model,
+ IRewriteSession rewriteSession,
+ Func, IRewriteSession, IDeclarationDeletionTargetFactory, IEnumerable> generateDeletionTargets)
+ {
+ var deletionTargets = generateDeletionTargets(model.Targets, rewriteSession, _declarationDeletionTargetFactory);
+
+ var targetsLookup = deletionTargets.ToLookup(dt => dt.TargetProxy.QualifiedModuleName);
+
+ foreach (var moduleQualifiedDeleteGroups in targetsLookup)
+ {
+ var deletionGroups = _declarationDeletionGroupsGenerator.Generate(moduleQualifiedDeleteGroups);
+
+ var rewriter = rewriteSession.CheckOutModuleRewriter(moduleQualifiedDeleteGroups.Key);
+
+ if (!model.DeleteDeclarationsOnly && model.DeleteAnnotations)
+ {
+ DeleteAnnotations(_declarationFinderProvider, deletionGroups, rewriter);
+ }
+
+ RemoveDeletionGroups(deletionGroups, model, rewriter);
+ }
+ }
+
+ protected void RemoveDeletionGroups(IEnumerable deletionGroups, IDeleteDeclarationsModel model, IModuleRewriter rewriter)
+ {
+ foreach (var deletionGroup in deletionGroups)
+ {
+ if (deletionGroup.OrderedPartialDeletionTargets.Any())
+ {
+ RemovePartialDeletionTargets(deletionGroup, model, rewriter);
+ }
+
+ if (deletionGroup.OrderedFullDeletionTargets.Any())
+ {
+ RemoveFullDeletionGroup(deletionGroup, model, rewriter);
+ }
+ }
+ }
+
+ protected void RemoveFullDeletionGroup(IDeclarationDeletionGroup deletionGroup, IDeleteDeclarationsModel model, IModuleRewriter rewriter)
+ {
+ foreach (var deleteTarget in deletionGroup.OrderedFullDeletionTargets)
+ {
+ DeleteTarget(deleteTarget, rewriter);
+ }
+
+ if (model.DeleteDeclarationsOnly)
+ {
+ return;
+ }
+
+ var lastTarget = deletionGroup.OrderedFullDeletionTargets.LastOrDefault();
+
+ foreach (var deleteTarget in deletionGroup.OrderedFullDeletionTargets.Where(t => t != lastTarget && t.TargetEOSContext != null))
+ {
+ rewriter.Remove(deleteTarget.TargetEOSContext);
+ }
+
+ if (lastTarget is null || lastTarget.TargetEOSContext is null)
+ {
+ return;
+ }
+
+ lastTarget.PrecedingEOSContext = GetPrecedingNonDeletedEOSContextForGroup(deletionGroup);
+
+ ModifyLastTargetEOS(lastTarget, model, rewriter);
+ }
+
+ // The default GetPrecedingNonDeletedEOSContextForGroup is overridden by DeleteModuleElementsRefactoringAction
+ // and DeleteProcedureScopeElementsRefactoringAction
+ protected virtual VBAParser.EndOfStatementContext GetPrecedingNonDeletedEOSContextForGroup(IDeclarationDeletionGroup deletionGroup)
+ => deletionGroup.Targets.FirstOrDefault()?.PrecedingEOSContext;
+
+ protected IEnumerable CreateDeletionTargetsSupportingPartialDeletions(IEnumerable declarations, IRewriteSession rewriteSession, IDeclarationDeletionTargetFactory targetFactory)
+ {
+ var deletionTargets = new List();
+
+ var remainingTargets = declarations.ToList();
+
+ while (remainingTargets.Any())
+ {
+ var deleteTarget = targetFactory.Create(remainingTargets.First(), rewriteSession);
+
+ if (deleteTarget.AllDeclarationsInListContext.Count >= 1)
+ {
+ var listContextRelatedTargets = deleteTarget.AllDeclarationsInListContext.Intersect(declarations);
+ deleteTarget.AddTargets(listContextRelatedTargets);
+ remainingTargets.RemoveAll(t => listContextRelatedTargets.Contains(t));
+ }
+ else
+ {
+ remainingTargets.RemoveAll(t => t == declarations.First());
+ }
+
+ deletionTargets.Add(deleteTarget);
+ }
+
+ return deletionTargets;
+ }
+
+ protected Action DeleteTarget { set; get; }
+ = (t, rewriter) => rewriter.Remove(t.DeleteContext);
+
+ ///
+ /// Replaces the EndOfStatementContext preceding the deletion group.
+ ///
+ ///
+ /// The preceding EndOfStatementContext is replaced with a merged version of the preceding EndOfStatementContext
+ /// and the last delete target's EndOfStatementContext.
+ ///
+ protected void ModifyLastTargetEOS(IDeclarationDeletionTarget lastTarget, IDeleteDeclarationsModel model, IModuleRewriter rewriter)
+ {
+ if (lastTarget.TargetEOSContext.GetText() == EOS_COLON)
+ {
+ //Remove the declarations EOS colon character and use the PrecedingEOSContext as-is
+ lastTarget.Rewriter.Remove(lastTarget.TargetEOSContext);
+ return;
+ }
+
+ ModifyRelatedComments(lastTarget, model, rewriter);
+
+ var replacementText = lastTarget.EOSContextToReplace == lastTarget.TargetEOSContext
+ ? lastTarget.ModifiedTargetEOSContent
+ : lastTarget.BuildEOSReplacementContent();
+
+ rewriter.Replace(lastTarget.EOSContextToReplace, replacementText);
+
+ if (lastTarget.DeletionIncludesEOSContext)
+ {
+ rewriter.Remove(lastTarget.TargetEOSContext);
+ }
+ }
+
+ protected static void ModifyRelatedComments(IDeclarationDeletionTarget deleteTarget, IDeleteDeclarationsModel model, IModuleRewriter rewriter)
+ {
+ var targetEOSComments = deleteTarget.TargetEOSContext.GetAllComments();
+
+ if (deleteTarget.IsFullDelete)
+ {
+ var declarationLogicalLineCommentContext = deleteTarget.GetDeclarationLogicalLineCommentContext();
+
+ if (model.DeleteDeclarationLogicalLineComments && declarationLogicalLineCommentContext != null)
+ {
+ DeleteDeclarationLogicalLineComments(deleteTarget, declarationLogicalLineCommentContext, rewriter);
+ targetEOSComments = targetEOSComments.Where(c => c != declarationLogicalLineCommentContext);
+ }
+ else if (!model.DeleteDeclarationLogicalLineComments && declarationLogicalLineCommentContext != null)
+ {
+ //If we are keeping the Declaration line comments, then insert a newline or it will end up on the
+ //same line as the last comment of the preceding EOSContext
+ rewriter.InsertBefore(declarationLogicalLineCommentContext.Start.TokenIndex, Environment.NewLine);
+ }
+ }
+
+ if (model.InsertValidationTODOForRetainedComments)
+ {
+ var injectedTODOContent = Resources.Refactorings.Refactorings.CommentVerification_TODO;
+
+ foreach (var comment in targetEOSComments.Concat(deleteTarget.PrecedingEOSContext.GetAllComments()))
+ {
+ var content = comment.GetText();
+ var indexOfFirstCommentMarker = content.IndexOf(Tokens.CommentMarker);
+ var newContent = $"{content.Substring(0, indexOfFirstCommentMarker)}{injectedTODOContent}{content.Substring(indexOfFirstCommentMarker + 1)}";
+ rewriter.Replace(comment, newContent);
+ }
+ }
+ }
+
+ ///
+ /// Deletes only those Annotations where ALL the Declarations referencing the same Annotation are selected for deletion.
+ ///
+ private static void DeleteAnnotations(IDeclarationFinderProvider declarationFinderProvider, IReadOnlyCollection deletionGroups, IModuleRewriter rewriter)
+ {
+ foreach (var deletionGroup in deletionGroups)
+ {
+ if (!TryGetDeletableAnnotations(deletionGroup, declarationFinderProvider, out var deletableAnnotations))
+ {
+ continue;
+ }
+
+ foreach (var annotation in deletableAnnotations)
+ {
+ if (annotation.TryGetAncestor(out var annotationListIndividualNonEOFEOSCtxt))
+ {
+ rewriter.Remove(annotationListIndividualNonEOFEOSCtxt);
+ }
+ }
+ }
+ }
+ private static bool TryGetDeletableAnnotations(IDeclarationDeletionGroup deletionGroup, IDeclarationFinderProvider declarationFinderProvider, out List deletableAnnotations)
+ {
+ deletableAnnotations = new List();
+
+ var relevantAnnotations = deletionGroup.Declarations
+ .SelectMany(d => d.Annotations)
+ .Select(a => a.Context)
+ .Distinct();
+
+ var moduleDeclarations = declarationFinderProvider.DeclarationFinder
+ .Members(deletionGroup.Declarations.First().QualifiedModuleName).ToList();
+
+ foreach (var annotation in relevantAnnotations)
+ {
+ var declarationsAssociatedWithAnnotation = moduleDeclarations
+ .Where(t => t.Annotations.Any(a => a.Context == annotation));
+
+ if (declarationsAssociatedWithAnnotation.Any(d => !deletionGroup.Declarations.Contains(d)))
+ {
+ continue;
+ }
+
+ deletableAnnotations.Add(annotation);
+ }
+
+ return deletableAnnotations.Any();
+ }
+
+ private static void DeleteDeclarationLogicalLineComments(IDeclarationDeletionTarget deleteTarget, VBAParser.CommentContext declarationLineCommentContext, IModuleRewriter rewriter)
+ {
+ if (declarationLineCommentContext is null)
+ {
+ return;
+ }
+
+ var individualNonEOFEOS = declarationLineCommentContext.GetAncestor();
+ var contextToDelete = individualNonEOFEOS.GetChild();
+
+ var ws = contextToDelete.GetDescendent();
+ var containsLineContinuation = ws?.GetText().Contains(Tokens.LineContinuation) ?? false;
+
+ if (contextToDelete != null && declarationLineCommentContext.Start.Line == deleteTarget.TargetEOSContext.Start.Line || containsLineContinuation)
+ {
+ rewriter.Remove(contextToDelete);
+ }
+ }
+
+ private void RemovePartialDeletionTargets(IDeclarationDeletionGroup deletionGroup, IDeleteDeclarationsModel model, IModuleRewriter rewriter)
+ {
+ var lastTarget = deletionGroup.OrderedPartialDeletionTargets.Last();
+
+ lastTarget.PrecedingEOSContext = GetPrecedingNonDeletedEOSContextForGroup(deletionGroup);
+
+ var retainedDeclarationsExpression = lastTarget.ListContext.GetText().Contains(_lineContinuationExpression)
+ ? $"{BuildDeclarationsExpressionWithLineContinuations(lastTarget)}"
+ : $"{string.Join(", ", lastTarget.RetainedDeclarations.Select(d => d.Context.GetText()))}";
+
+ rewriter.Replace(lastTarget.ListContext.Parent, $"{GetDeclarationScopeExpression(lastTarget.TargetProxy)} {retainedDeclarationsExpression}");
+
+ if (lastTarget is null || lastTarget.TargetEOSContext is null)
+ {
+ return;
+ }
+
+ if (lastTarget.TargetEOSContext.GetText() == EOS_COLON)
+ {
+ //Remove the declarations EOS colon character and use the PrecedingEOSContext as-is
+ lastTarget.Rewriter.Remove(lastTarget.TargetEOSContext);
+ return;
+ }
+
+ ModifyRelatedComments(lastTarget, model, rewriter);
+
+ rewriter.Replace(lastTarget.TargetEOSContext, lastTarget.ModifiedTargetEOSContent);
+ }
+
+ private static string BuildDeclarationsExpressionWithLineContinuations(IDeclarationDeletionTarget deleteDeclarationTarget)
+ {
+ var elementsByLineContinuation = deleteDeclarationTarget.ListContext.GetText().Split(new string[] { _lineContinuationExpression }, StringSplitOptions.None);
+
+ if (elementsByLineContinuation.Count() == 1)
+ {
+ throw new ArgumentException("'targetsToDelete' parameter does not contain line extension(s)");
+ }
+
+ var expr = new StringBuilder();
+ foreach (var element in elementsByLineContinuation)
+ {
+ var idContexts = deleteDeclarationTarget.RetainedDeclarations.Where(r => element.Contains(r.Context.GetText())).Select(d => d);
+ foreach (var ctxt in idContexts)
+ {
+ var indent = string.Concat(element.TakeWhile(e => e == ' '));
+
+ expr = expr.Length == 0
+ ? expr.Append(ctxt.Context.GetText())
+ : expr.Append($",{_lineContinuationExpression}{indent}{ctxt.Context.GetText()}");
+ }
+ }
+ return expr.ToString();
+ }
+
+ private static string GetDeclarationScopeExpression(Declaration listPrototype)
+ {
+ if (listPrototype.DeclarationType.HasFlag(DeclarationType.Variable))
+ {
+ var accessToken = listPrototype.Accessibility == Accessibility.Implicit
+ ? Tokens.Private
+ : $"{listPrototype.Accessibility}";
+
+ return listPrototype.ParentDeclaration is ModuleDeclaration
+ ? accessToken
+ : Tokens.Dim;
+ }
+
+ if (listPrototype.DeclarationType.HasFlag(DeclarationType.Constant))
+ {
+ var accessToken = listPrototype.Accessibility == Accessibility.Implicit
+ ? Tokens.Private
+ : $"{listPrototype.Accessibility}";
+
+ return listPrototype.ParentDeclaration is ModuleDeclaration
+ ? $"{accessToken} {Tokens.Const}"
+ : Tokens.Const;
+ }
+
+ throw new ArgumentException("Unsupported DeclarationType");
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionFactories.cs b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionFactories.cs
new file mode 100644
index 0000000000..0046e5a450
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionFactories.cs
@@ -0,0 +1,27 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Refactorings.DeleteDeclarations;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings
+{
+ public interface IDeclarationDeletionTargetFactory
+ {
+ IDeclarationDeletionTarget Create(Declaration declaration, IRewriteSession rewriteSession);
+
+ IEnumerable CreateMany(IEnumerable declarations, IRewriteSession rewriteSession);
+ }
+
+ public interface IDeclarationDeletionGroupFactory
+ {
+ IDeclarationDeletionGroup Create(IOrderedEnumerable deletionTargets);
+ }
+
+ public interface IDeclarationDeletionGroupsGeneratorFactory
+ {
+ IDeclarationDeletionGroupsGenerator Create();
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionGroup.cs b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionGroup.cs
new file mode 100644
index 0000000000..6c273cc858
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionGroup.cs
@@ -0,0 +1,20 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing.Symbols;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings
+{
+ public interface IDeclarationDeletionGroup
+ {
+ IEnumerable Declarations { get; }
+
+ IOrderedEnumerable OrderedFullDeletionTargets { get; }
+
+ IOrderedEnumerable OrderedPartialDeletionTargets { get; }
+
+ ParserRuleContext PrecedingNonDeletedContext { set; get; }
+
+ IReadOnlyCollection Targets { get; }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionGroupGenerator.cs b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionGroupGenerator.cs
new file mode 100644
index 0000000000..98c0f5740b
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionGroupGenerator.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Rubberduck.Refactorings
+{
+ public interface IDeclarationDeletionGroupsGenerator
+ {
+ List Generate(IEnumerable declarationDeletionTargets);
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionTarget.cs b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionTarget.cs
new file mode 100644
index 0000000000..a413ed7338
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/Abstract/IDeclarationDeletionTarget.cs
@@ -0,0 +1,85 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using System.Collections.Generic;
+
+namespace Rubberduck.Refactorings
+{
+ public interface IModuleElementDeletionTarget : IDeclarationDeletionTarget
+ { }
+
+ public interface IPropertyDeletionTarget
+ {
+ bool IsGroupedWithRelatedProperties();
+ }
+
+ public interface IEnumMemberDeletionTarget : IDeclarationDeletionTarget
+ {}
+
+ public interface IUdtMemberDeletionTarget : IDeclarationDeletionTarget
+ {}
+
+ public interface ILocalScopeDeletionTarget : IDeclarationDeletionTarget
+ {
+ ILocalScopeDeletionTarget AssociatedLabelToDelete { get; }
+
+ bool IsLabel(out ILabelDeletionTarget labelTarget);
+
+ ParserRuleContext ScopingContext { get; }
+ bool HasSameLogicalLineLabel(out VBAParser.StatementLabelDefinitionContext labelContext);
+
+ void SetupToDeleteAssociatedLabel(ILabelDeletionTarget label);
+
+ }
+
+ public interface ILabelDeletionTarget
+ {
+ bool HasSameLogicalLineListContext(out ParserRuleContext varOrConst);
+
+ bool HasFollowingMainBlockStatementContext(out VBAParser.MainBlockStmtContext mainBlockStmtContext);
+
+ bool ReplaceLabelWithWhitespace { set; get; }
+ }
+
+ public interface IDeclarationDeletionTarget
+ {
+ bool IsFullDelete { get; }
+
+ void AddTargets(IEnumerable targets);
+
+ ///
+ /// TargetProxy is the target for DeclarationTypes that are not declared
+ /// in a list, or the first Declaration in a Declaration List.
+ ///
+ Declaration TargetProxy { get; }
+
+ IReadOnlyCollection Declarations { get; }
+
+ IModuleRewriter Rewriter { get; }
+
+ IReadOnlyList AllDeclarationsInListContext { get; }
+
+ IEnumerable RetainedDeclarations { get; }
+
+ VBAParser.EndOfStatementContext PrecedingEOSContext { set; get; }
+
+ VBAParser.EndOfStatementContext TargetEOSContext { get; }
+
+ VBAParser.EndOfStatementContext EOSContextToReplace { get; }
+
+ ParserRuleContext DeleteContext { get; }
+
+ ParserRuleContext ListContext { get; }
+
+ ParserRuleContext TargetContext { get; }
+
+ VBAParser.CommentContext GetDeclarationLogicalLineCommentContext();
+
+ string BuildEOSReplacementContent();
+
+ string ModifiedTargetEOSContent { get; }
+
+ bool DeletionIncludesEOSContext { get; }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsExtensions.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsExtensions.cs
new file mode 100644
index 0000000000..5e59d0e5b7
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsExtensions.cs
@@ -0,0 +1,62 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ internal static class ParserRuleContextExtensions
+ {
+ public static IEnumerable GetChildrenOfType(this ParserRuleContext parent) where T : ParserRuleContext
+ => parent?.children?.OfType().Cast() ?? Enumerable.Empty();
+
+ public static string CurrentContent(this ParserRuleContext context, IModuleRewriter rewriter)
+ => context != null ? rewriter.GetText(context.Start.TokenIndex, context.Stop.TokenIndex) : string.Empty;
+
+ public static VBAParser.EndOfStatementContext GetFollowingEndOfStatementContext(this ParserRuleContext context)
+ {
+ context.TryGetFollowingContext(out VBAParser.EndOfStatementContext eos);
+ return eos;
+ }
+
+ public static VBAParser.EndOfStatementContext GetPrecedingEndOfStatementContext(this ParserRuleContext context)
+ {
+ context.TryGetPrecedingContext(out VBAParser.EndOfStatementContext eos);
+ return eos;
+ }
+ }
+
+ internal static class EndOfStatementContextExtensions
+ {
+ private const string EndOfStatementColon = ": ";
+
+ public static string GetSeparation(this VBAParser.EndOfStatementContext eosContext)
+ => string.Concat(eosContext.GetSeparationAndIndentationContent().TakeWhile(c => c != ' '));
+
+ public static string GetIndentation(this VBAParser.EndOfStatementContext eosContext)
+ => string.Concat(eosContext.GetSeparationAndIndentationContent().SkipWhile(c => c == '\r' || c == '\n'));
+
+ public static string GetSeparationAndIndentationContent(this VBAParser.EndOfStatementContext eosContext)
+ {
+ var eosContent = eosContext?.GetText() ?? string.Empty;
+ if ((eosContent?.Length ?? 0) <= 0)
+ {
+ return string.Empty;
+ }
+
+ return eosContent.StartsWith(EndOfStatementColon)
+ ? string.Empty
+ : Regex.Match(eosContent, @"(\r\n)+\s*$").Value;
+ }
+
+ public static IEnumerable GetAllComments(this VBAParser.EndOfStatementContext eosContext)
+ => eosContext?.children.OfType()
+ .Select(child => child.GetDescendent())
+ .Where(ch => ch != null)
+ ?? Enumerable.Empty();
+
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsModel.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsModel.cs
new file mode 100644
index 0000000000..28cedc805f
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsModel.cs
@@ -0,0 +1,74 @@
+using Rubberduck.Parsing.Symbols;
+using System.Collections.Generic;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public interface IDeleteDeclarationsModel : IRefactoringModel
+ {
+ IEnumerable Targets { get; }
+
+ ///
+ /// When set to true, this flag overides all other flags and prevents changes to Annotations and Comments.
+ /// Default value is false.
+ ///
+ bool DeleteDeclarationsOnly { set; get; }
+
+ ///
+ /// when set to true, this flag enables edits to comments adjacent to delete targets. Default value is true.
+ ///
+ bool InsertValidationTODOForRetainedComments { set; get; }
+
+ ///
+ /// When set to true, this flag enables the deletion of comments found on the same logical line as a
+ /// deleted Declaration. Default value is true.
+ ///
+ bool DeleteDeclarationLogicalLineComments { set; get; }
+
+ ///
+ /// When set to true, this flag enables the removal of Annotations exclusively associated with a set of deleted Declarations.
+ /// Default value is true.
+ ///
+ bool DeleteAnnotations { set; get; }
+ }
+
+ public class DeleteDeclarationsModel : IDeleteDeclarationsModel
+ {
+ private readonly HashSet _targets = new HashSet();
+
+ public DeleteDeclarationsModel()
+ {}
+
+ public DeleteDeclarationsModel(params Declaration[] targets)
+ {
+ AddRangeOfDeclarationsToDelete(targets);
+ }
+
+ public DeleteDeclarationsModel(IEnumerable targets)
+ {
+ AddRangeOfDeclarationsToDelete(targets);
+ }
+
+ public void AddDeclarationsToDelete(params Declaration[] targets)
+ {
+ AddRangeOfDeclarationsToDelete(targets);
+ }
+
+ public void AddRangeOfDeclarationsToDelete(IEnumerable targets)
+ {
+ foreach (var t in targets)
+ {
+ _targets.Add(t);
+ }
+ }
+
+ public IEnumerable Targets => new List(_targets);
+
+ public bool DeleteDeclarationsOnly { set; get; } = false;
+
+ public bool InsertValidationTODOForRetainedComments { set; get; } = true;
+
+ public bool DeleteDeclarationLogicalLineComments { set; get; } = true;
+
+ public bool DeleteAnnotations { set; get; } = true;
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsRefactoringAction.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsRefactoringAction.cs
new file mode 100644
index 0000000000..4560679f13
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteDeclarationsRefactoringAction.cs
@@ -0,0 +1,225 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Refactorings.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ ///
+ /// Removes 0 to n Declarations along with associated Annotations. Removes comments on the same logical line
+ /// as the removed Declaration. Other surrounding comments are edited to include a TODO statement indicating
+ /// that the user should evaluate if the comment is still valid.
+ /// Warning: If references to the removed Declarations are not removed/modified by other code,
+ /// this refactoring action will generate uncompilable code.
+ ///
+ public class DeleteDeclarationsRefactoringAction : CodeOnlyRefactoringActionBase
+ {
+ private readonly ICodeOnlyRefactoringAction _deleteModuleElementsRefactoringAction;
+ private readonly ICodeOnlyRefactoringAction _deleteProcedureScopeElementsRefactoringAction;
+ private readonly ICodeOnlyRefactoringAction _deleteUDTMembersRefactoringAction;
+ private readonly ICodeOnlyRefactoringAction _deleteEnumMembersRefactoringAction;
+
+ private static List _supportedDeclarationTypes = new List()
+ {
+ DeclarationType.Variable,
+ DeclarationType.Constant,
+ DeclarationType.Function,
+ DeclarationType.Procedure,
+ DeclarationType.PropertyGet,
+ DeclarationType.PropertyLet,
+ DeclarationType.PropertySet,
+ DeclarationType.UserDefinedType,
+ DeclarationType.UserDefinedTypeMember,
+ DeclarationType.Enumeration,
+ DeclarationType.EnumerationMember,
+ DeclarationType.LineLabel
+ };
+
+ public DeleteDeclarationsRefactoringAction(DeleteModuleElementsRefactoringAction deleteModuleElementsRefactoringAction,
+ DeleteProcedureScopeElementsRefactoringAction deleteProcedureScopeElementsRefactoringAction,
+ DeleteUDTMembersRefactoringAction deleteUDTMembersRefactoringAction,
+ DeleteEnumMembersRefactoringAction deleteEnumMembersRefactoringAction,
+ IRewritingManager rewritingManager)
+ : base(rewritingManager)
+ {
+ _deleteModuleElementsRefactoringAction = deleteModuleElementsRefactoringAction;
+ _deleteProcedureScopeElementsRefactoringAction = deleteProcedureScopeElementsRefactoringAction;
+ _deleteUDTMembersRefactoringAction = deleteUDTMembersRefactoringAction;
+ _deleteEnumMembersRefactoringAction = deleteEnumMembersRefactoringAction;
+ }
+
+ public override void Refactor(DeleteDeclarationsModel model, IRewriteSession rewriteSession)
+ {
+ if (!model.Targets.Any())
+ {
+ return;
+ }
+
+ if (model.Targets.Any(t => !_supportedDeclarationTypes.Contains(t.DeclarationType)))
+ {
+ var invalidDeclaration = model.Targets.First(t => !_supportedDeclarationTypes.Contains(t.DeclarationType));
+ throw new InvalidDeclarationTypeException(invalidDeclaration);
+ }
+
+ //Minimize/optimize target list to prevent Rewriter boundary overlap errors and uncompilable code scenarios
+ var minimizedTargets = RemoveTargetChildren(model.Targets);
+ minimizedTargets = ReplaceDeleteAllUDTMembersOrEnumMembersWithParent(minimizedTargets);
+
+ var targetsGroup = GroupTargetsByRefactoringActionScope(minimizedTargets);
+
+ DeleteTargets(targetsGroup.ModuleScope, model, rewriteSession);
+ DeleteTargets(targetsGroup.ProcedureScope, model, rewriteSession);
+ DeleteTargets(targetsGroup.EnumMembers, model, rewriteSession);
+ DeleteTargets(targetsGroup.UdtMembers, model, rewriteSession);
+ }
+
+ private void DeleteTargets(IEnumerable targets, DeleteDeclarationsModel model, IRewriteSession rewriteSession) where T : DeleteDeclarationsModel, new()
+ {
+ if (!targets.Any())
+ {
+ return;
+ }
+
+ var tModel = CreateCodeOnlyRefactoringModel(targets, model);
+
+ switch (tModel)
+ {
+ case DeleteModuleElementsModel deleteModuleElementsModel:
+ _deleteModuleElementsRefactoringAction.Refactor(deleteModuleElementsModel, rewriteSession);
+ return;
+ case DeleteProcedureScopeElementsModel procedureScopeElementsModel:
+ _deleteProcedureScopeElementsRefactoringAction.Refactor(procedureScopeElementsModel, rewriteSession);
+ return;
+ case DeleteEnumMembersModel enumMembersModel:
+ _deleteEnumMembersRefactoringAction.Refactor(enumMembersModel, rewriteSession);
+ return;
+ case DeleteUDTMembersModel udtMembersModel:
+ _deleteUDTMembersRefactoringAction.Refactor(udtMembersModel, rewriteSession);
+ return;
+ default:
+ throw new ArgumentException();
+ }
+ }
+
+ private static (IEnumerable ModuleScope,
+ IEnumerable ProcedureScope,
+ IEnumerable EnumMembers,
+ IEnumerable UdtMembers)
+ GroupTargetsByRefactoringActionScope(IEnumerable targets)
+ {
+ var moduleScopeTargets = targets.Where(t => t.ParentDeclaration is ModuleDeclaration);
+
+ var enumMemberTargets = targets.Where(t => t.DeclarationType.HasFlag(DeclarationType.EnumerationMember));
+
+ var udtMemberTargets = targets.Where(t => t.DeclarationType.HasFlag(DeclarationType.UserDefinedTypeMember));
+
+ var procedureScopeTargets = targets
+ .Except(moduleScopeTargets)
+ .Except(enumMemberTargets)
+ .Except(udtMemberTargets);
+
+ return (moduleScopeTargets, procedureScopeTargets, enumMemberTargets, udtMemberTargets);
+ }
+
+ ///
+ /// Removes targets where the Parent declaration is also in the list of deletion targets
+ ///
+ private static List RemoveTargetChildren(IEnumerable targets)
+ {
+ var declarationTypes = new List()
+ {
+ DeclarationType.Member,
+ DeclarationType.Enumeration,
+ DeclarationType.UserDefinedType
+ };
+
+ var optimizedTargets = targets.ToList();
+
+ foreach (var decType in declarationTypes)
+ {
+ var parentDeclarations = targets.Where(t => t.DeclarationType.HasFlag(decType));
+ var toRemove = targets.Where(t => parentDeclarations.Contains(t.ParentDeclaration));
+ optimizedTargets.RemoveAll(t => toRemove.Contains(t));
+ }
+ return optimizedTargets;
+ }
+
+ ///
+ /// If all members of an Enum or UserDefinedType are targeted for deletion, adds the UserDefinedType
+ /// and/or Enum declaration to the target list and removes the associated Members. Both UDT and
+ /// Enum Types must have at least one member to compile.
+ ///
+ private static List ReplaceDeleteAllUDTMembersOrEnumMembersWithParent(List targets)
+ {
+ List toRemove = new List();
+ List toAdd = new List();
+
+ //Replace members with the Parent declaration if all the members are in the list of targets
+ var modifiesEnumMemberTargets = RequiresEnumDeclarationDeletion(targets, ref toRemove, ref toAdd);
+
+ var modifiesUDTMemberTargets = RequiresUserDefinedTypeDeclarationDeletion(targets, ref toRemove, ref toAdd);
+
+ if (modifiesEnumMemberTargets || modifiesUDTMemberTargets)
+ {
+ targets.RemoveAll(t => toRemove.Contains(t));
+ targets.AddRange(toAdd);
+ }
+
+ return targets;
+ }
+
+ private static bool RequiresEnumDeclarationDeletion(List targets, ref List toRemove, ref List toAdd)
+ {
+
+ bool ContainsAllMembers(ParserRuleContext ctxt, IEnumerable declarations)
+ => ctxt.children.Where(ch => ch is VBAParser.EnumerationStmt_ConstantContext).Count() == declarations.Count();
+
+ var enumMembers = targets.Where(t => t.DeclarationType == DeclarationType.EnumerationMember);
+ return RequiresParentDeclarationDeletion(enumMembers, ContainsAllMembers, ref toRemove, ref toAdd);
+ }
+
+ private static bool RequiresUserDefinedTypeDeclarationDeletion(List targets, ref List toRemove, ref List toAdd)
+ {
+
+ bool ContainsAllMembers(ParserRuleContext ctxt, IEnumerable declarations)
+ => ctxt.GetChild()
+ .children.Where(ch => ch is VBAParser.UdtMemberContext).Count() == declarations.Count();
+
+ var udtMembers = targets.Where(t => t.DeclarationType == DeclarationType.UserDefinedTypeMember);
+ return RequiresParentDeclarationDeletion(udtMembers, ContainsAllMembers, ref toRemove, ref toAdd);
+ }
+
+ private static bool RequiresParentDeclarationDeletion(IEnumerable targets, Func, bool> requiresParentDeletion, ref List toRemove, ref List toAdd)
+ {
+ foreach (var tGroup in targets.ToLookup(key => key.ParentDeclaration))
+ {
+ if (requiresParentDeletion(tGroup.Key.Context, tGroup))
+ {
+ toRemove.AddRange(tGroup);
+ toAdd.Add(tGroup.Key);
+ }
+ }
+
+ return toRemove.Count > 0;
+ }
+
+ private static T CreateCodeOnlyRefactoringModel(IEnumerable targets, DeleteDeclarationsModel model) where T: DeleteDeclarationsModel, new()
+ {
+ var newModel = new T()
+ {
+ InsertValidationTODOForRetainedComments = model.InsertValidationTODOForRetainedComments,
+ DeleteDeclarationLogicalLineComments = model.DeleteDeclarationLogicalLineComments,
+ DeleteAnnotations = model.DeleteAnnotations,
+ DeleteDeclarationsOnly = model.DeleteDeclarationsOnly
+ };
+
+ newModel.AddRangeOfDeclarationsToDelete(targets);
+ return newModel;
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteEnumMembers/DeleteEnumMembersModel.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteEnumMembers/DeleteEnumMembersModel.cs
new file mode 100644
index 0000000000..8472b87a7f
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteEnumMembers/DeleteEnumMembersModel.cs
@@ -0,0 +1,15 @@
+using Rubberduck.Parsing.Symbols;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public class DeleteEnumMembersModel : DeleteDeclarationsModel
+ {
+ public DeleteEnumMembersModel()
+ : base(Enumerable.Empty()) { }
+
+ public DeleteEnumMembersModel(IEnumerable targets)
+ :base(targets){}
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteEnumMembers/DeleteEnumMembersRefactoringAction.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteEnumMembers/DeleteEnumMembersRefactoringAction.cs
new file mode 100644
index 0000000000..4de89e47b2
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteEnumMembers/DeleteEnumMembersRefactoringAction.cs
@@ -0,0 +1,32 @@
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using Rubberduck.Refactorings.DeleteDeclarations.Abstract;
+using System;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public class DeleteEnumMembersRefactoringAction : DeleteElementsRefactoringActionBase
+ {
+ public DeleteEnumMembersRefactoringAction(IDeclarationFinderProvider declarationFinderProvider,
+ IDeclarationDeletionTargetFactory targetFactory,
+ IDeclarationDeletionGroupsGeneratorFactory deletionGroupsGeneratorFactory,
+ IRewritingManager rewritingManager)
+ : base(declarationFinderProvider, targetFactory, deletionGroupsGeneratorFactory, rewritingManager)
+ {}
+
+ public override void Refactor(DeleteEnumMembersModel model, IRewriteSession rewriteSession)
+ {
+ if (!CanRefactorAllTargets(model))
+ {
+ throw new InvalidOperationException("Only DeclarationType.EnumerationMember can be refactored by this class");
+ }
+
+ DeleteDeclarations(model, rewriteSession, (declarations, rewriterSession, targetFactory) => targetFactory.CreateMany(declarations, rewriteSession));
+ }
+
+ protected override bool CanRefactorAllTargets(DeleteEnumMembersModel model)
+ => model.Targets.All(t => t.DeclarationType == DeclarationType.EnumerationMember);
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteModuleElements/DeleteModuleElementsModel.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteModuleElements/DeleteModuleElementsModel.cs
new file mode 100644
index 0000000000..9afc8cb801
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteModuleElements/DeleteModuleElementsModel.cs
@@ -0,0 +1,15 @@
+using System.Collections.Generic;
+using Rubberduck.Parsing.Symbols;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public class DeleteModuleElementsModel : DeleteDeclarationsModel
+ {
+ public DeleteModuleElementsModel()
+ : base(Enumerable.Empty()) { }
+
+ public DeleteModuleElementsModel(IEnumerable targets)
+ : base(targets) { }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteModuleElements/DeleteModuleElementsRefactoringAction.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteModuleElements/DeleteModuleElementsRefactoringAction.cs
new file mode 100644
index 0000000000..4cc86ef227
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteModuleElements/DeleteModuleElementsRefactoringAction.cs
@@ -0,0 +1,47 @@
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using Rubberduck.Refactorings.DeleteDeclarations.Abstract;
+using System;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public class DeleteModuleElementsRefactoringAction : DeleteElementsRefactoringActionBase
+ {
+ public DeleteModuleElementsRefactoringAction(IDeclarationFinderProvider declarationFinderProvider,
+ IDeclarationDeletionTargetFactory targetFactory,
+ IDeclarationDeletionGroupsGeneratorFactory deletionGroupsGeneratorFactory,
+ IRewritingManager rewritingManager)
+ : base(declarationFinderProvider, targetFactory, deletionGroupsGeneratorFactory, rewritingManager)
+ {}
+
+ public override void Refactor(DeleteModuleElementsModel model, IRewriteSession rewriteSession)
+ {
+ if (!CanRefactorAllTargets(model))
+ {
+ throw new InvalidOperationException("Only module-scope declarations can be refactored by this object");
+ }
+
+ DeleteDeclarations(model, rewriteSession, base.CreateDeletionTargetsSupportingPartialDeletions);
+ }
+
+ protected override bool CanRefactorAllTargets(DeleteModuleElementsModel model)
+ => model.Targets.All(t => t.ParentDeclaration is ModuleDeclaration);
+
+ protected override VBAParser.EndOfStatementContext GetPrecedingNonDeletedEOSContextForGroup(IDeclarationDeletionGroup deletionGroup)
+ {
+ var deleteTarget = deletionGroup.Targets.FirstOrDefault();
+ if (!(deleteTarget is IModuleElementDeletionTarget))
+ {
+ throw new ArgumentException();
+ }
+
+ return deletionGroup.PrecedingNonDeletedContext?.GetFollowingEndOfStatementContext()
+ ?? deleteTarget.TargetContext.GetAncestor()
+ .GetChild();
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteProcedureScopeElements/DeleteProcedureScopeElementsModel.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteProcedureScopeElements/DeleteProcedureScopeElementsModel.cs
new file mode 100644
index 0000000000..49d9e1d84f
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteProcedureScopeElements/DeleteProcedureScopeElementsModel.cs
@@ -0,0 +1,15 @@
+using Rubberduck.Parsing.Symbols;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public class DeleteProcedureScopeElementsModel : DeleteDeclarationsModel
+ {
+ public DeleteProcedureScopeElementsModel()
+ : base(Enumerable.Empty()) { }
+
+ public DeleteProcedureScopeElementsModel(IEnumerable targets)
+ : base(targets) { }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteProcedureScopeElements/DeleteProcedureScopeElementsRefactoringAction.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteProcedureScopeElements/DeleteProcedureScopeElementsRefactoringAction.cs
new file mode 100644
index 0000000000..cf6c53f28a
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteProcedureScopeElements/DeleteProcedureScopeElementsRefactoringAction.cs
@@ -0,0 +1,112 @@
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using Rubberduck.Refactorings.DeleteDeclarations.Abstract;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public class DeleteProcedureScopeElementsRefactoringAction : DeleteElementsRefactoringActionBase
+ {
+ public DeleteProcedureScopeElementsRefactoringAction(IDeclarationFinderProvider declarationFinderProvider,
+ IDeclarationDeletionTargetFactory targetFactory,
+ IDeclarationDeletionGroupsGeneratorFactory deletionGroupsGeneratorFactory,
+ IRewritingManager rewritingManager)
+ : base(declarationFinderProvider, targetFactory, deletionGroupsGeneratorFactory, rewritingManager)
+ {
+ DeleteTarget = DeleteLocalTarget;
+ }
+
+ public override void Refactor(DeleteProcedureScopeElementsModel model, IRewriteSession rewriteSession)
+ {
+ if (!CanRefactorAllTargets(model))
+ {
+ throw new InvalidOperationException("Only declarations within procedures other than parameters can be refactored by this object");
+ }
+
+ DeleteDeclarations(model, rewriteSession, CreateDeletionTargetsLocalScope);
+ }
+
+ protected override bool CanRefactorAllTargets(DeleteProcedureScopeElementsModel model)
+ => model.Targets.All(t => t.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Member) && !t.DeclarationType.HasFlag(DeclarationType.Parameter));
+
+ //Creating local targets are the same as creating a module level deletion targets except that Labels
+ //need to be accounted for
+ private IEnumerable CreateDeletionTargetsLocalScope(IEnumerable declarations, IRewriteSession rewriteSession, IDeclarationDeletionTargetFactory targetFactory)
+ {
+ var allLocalScopeTargets = base.CreateDeletionTargetsSupportingPartialDeletions(declarations, rewriteSession, targetFactory)
+ .Cast();
+
+ var labelTargets = allLocalScopeTargets.Where(t => t.IsLabel(out _)).Cast().ToList();
+ if (!labelTargets.Any())
+ {
+ return allLocalScopeTargets;
+ }
+
+ var variableOrConstantTargets = allLocalScopeTargets.Except(labelTargets.Cast()).ToList();
+
+ var labelTargetsDeletedByAssociation = new List();
+ foreach (var labelTarget in labelTargets)
+ {
+ if (!labelTarget.HasSameLogicalLineListContext(out var relatedVarOrConstListContext))
+ {
+ continue;
+ }
+
+ //When a Label and Variable/Const Declaration are on the same logical line AND both are to be deleted,
+ //associate the Label declaration with the Declaration ListContext. Deleting the Declaration will
+ //result in deleting the Label as well because the entire BlockStmtContext will be deleted.
+ var relatedTarget = variableOrConstantTargets.SingleOrDefault(t => t.ListContext == relatedVarOrConstListContext);
+ if (relatedTarget != null)
+ {
+ relatedTarget.SetupToDeleteAssociatedLabel(labelTarget);
+
+ labelTargetsDeletedByAssociation.Add(labelTarget);
+ }
+ }
+
+ labelTargets.RemoveAll(t => labelTargetsDeletedByAssociation.Contains(t));
+
+ //A deleted Label that has content in the same VBAParser.BlockStmtContext replaces the label expression with equivalent whitespace
+ foreach (var labelTarget in labelTargets)
+ {
+ labelTarget.ReplaceLabelWithWhitespace = labelTarget.HasSameLogicalLineListContext(out var varOrConstListContext)
+ ? !variableOrConstantTargets.Any(t => t.ListContext == varOrConstListContext)
+ : labelTarget.HasFollowingMainBlockStatementContext(out _);
+ }
+
+ return variableOrConstantTargets.Concat(labelTargets.Cast()).ToList();
+ }
+
+ private static void DeleteLocalTarget(IDeclarationDeletionTarget deleteTarget, IModuleRewriter rewriter)
+ {
+ if (deleteTarget is ILabelDeletionTarget lblTarget && lblTarget.ReplaceLabelWithWhitespace)
+ {
+ var labelContent = deleteTarget.DeleteContext.GetText();
+ var spaces = string.Concat(Enumerable.Repeat(' ', labelContent.Length));
+ rewriter.Replace(deleteTarget.DeleteContext, spaces);
+ return;
+ }
+
+ rewriter.Remove(deleteTarget.DeleteContext);
+ }
+
+ protected override VBAParser.EndOfStatementContext GetPrecedingNonDeletedEOSContextForGroup(IDeclarationDeletionGroup deletionGroup)
+ {
+ var firstTarget = deletionGroup.Targets.FirstOrDefault();
+ if (!(firstTarget is ILocalScopeDeletionTarget localScopeTarget))
+ {
+ throw new ArgumentException();
+ }
+
+ return deletionGroup.PrecedingNonDeletedContext?.GetFollowingEndOfStatementContext()
+ ?? (deletionGroup.OrderedFullDeletionTargets.LastOrDefault() == firstTarget || !firstTarget.IsFullDelete
+ ? localScopeTarget.ScopingContext.GetPrecedingEndOfStatementContext()
+ : localScopeTarget.ScopingContext.GetChild());
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteUdtMembers/DeleteUDTMembersModel.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteUdtMembers/DeleteUDTMembersModel.cs
new file mode 100644
index 0000000000..d4e46ddee6
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteUdtMembers/DeleteUDTMembersModel.cs
@@ -0,0 +1,15 @@
+using Rubberduck.Parsing.Symbols;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public class DeleteUDTMembersModel : DeleteDeclarationsModel
+ {
+ public DeleteUDTMembersModel()
+ : base(Enumerable.Empty()) { }
+
+ public DeleteUDTMembersModel(IEnumerable targets)
+ : base(targets) { }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeleteUdtMembers/DeleteUDTMembersRefactoringAction.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeleteUdtMembers/DeleteUDTMembersRefactoringAction.cs
new file mode 100644
index 0000000000..a846f73860
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeleteUdtMembers/DeleteUDTMembersRefactoringAction.cs
@@ -0,0 +1,32 @@
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using Rubberduck.Refactorings.DeleteDeclarations.Abstract;
+using System;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public class DeleteUDTMembersRefactoringAction : DeleteElementsRefactoringActionBase
+ {
+ public DeleteUDTMembersRefactoringAction(IDeclarationFinderProvider declarationFinderProvider,
+ IDeclarationDeletionTargetFactory targetFactory,
+ IDeclarationDeletionGroupsGeneratorFactory deletionGroupsGeneratorFactory,
+ IRewritingManager rewritingManager)
+ : base(declarationFinderProvider, targetFactory, deletionGroupsGeneratorFactory, rewritingManager)
+ {}
+
+ public override void Refactor(DeleteUDTMembersModel model, IRewriteSession rewriteSession)
+ {
+ if (!CanRefactorAllTargets(model))
+ {
+ throw new InvalidOperationException("Only DeclarationType.UserDefinedTypeMember can be refactored by this class");
+ }
+
+ DeleteDeclarations(model, rewriteSession, (declarations, rewriterSession, targetFactory) => targetFactory.CreateMany(declarations, rewriteSession));
+ }
+
+ protected override bool CanRefactorAllTargets(DeleteUDTMembersModel model)
+ => model.Targets.Any(t => t.DeclarationType == DeclarationType.UserDefinedTypeMember);
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/DeclarationDeletionGroup.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/DeclarationDeletionGroup.cs
new file mode 100644
index 0000000000..4d87c4ffef
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/DeclarationDeletionGroup.cs
@@ -0,0 +1,27 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing.Symbols;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings
+{
+ internal class DeclarationDeletionGroup : IDeclarationDeletionGroup
+ {
+ public DeclarationDeletionGroup(IOrderedEnumerable deletionTargets)
+ {
+ Targets = deletionTargets.ToList();
+ OrderedFullDeletionTargets = deletionTargets.Where(dt => dt.IsFullDelete).OrderBy(dt => dt);
+ OrderedPartialDeletionTargets = deletionTargets.Where(dt => !dt.IsFullDelete).OrderBy(dt => dt);
+ }
+
+ public ParserRuleContext PrecedingNonDeletedContext { set; get; }
+
+ public IReadOnlyCollection Targets { private set; get; }
+
+ public IEnumerable Declarations => Targets.SelectMany(t => t.Declarations);
+
+ public IOrderedEnumerable OrderedFullDeletionTargets { private set; get; }
+
+ public IOrderedEnumerable OrderedPartialDeletionTargets { private set; get; }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/DeletionGroupsGenerator.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/DeletionGroupsGenerator.cs
new file mode 100644
index 0000000000..8b1650f306
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/DeletionGroupsGenerator.cs
@@ -0,0 +1,286 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Refactorings.DeleteDeclarations;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings
+{
+ internal class DeletionGroupsGenerator : IDeclarationDeletionGroupsGenerator
+ {
+ ///
+ /// DeletionGroupsGeneratorModel represents the data required to generate 1 to n DeclarationDeletionGroups.
+ /// Each DeclarationDeletionGroup represents an ordered set of adjacent delete targets within a given scope (module, procedure, etc).
+ ///
+ private struct DeletionGroupsGeneratorModel
+ {
+ public List Targets { set; get; }
+
+ public IOrderedEnumerable OrderedContexts { set; get; }
+
+ public string AllScopedTargetsDeletedExceptionMessage { set; get; }
+ }
+
+ private readonly IDeclarationDeletionGroupFactory _declarationDeletionGroupFactory;
+
+ public DeletionGroupsGenerator(IDeclarationDeletionGroupFactory declarationDeletionGroupFactory)
+ {
+ _declarationDeletionGroupFactory = declarationDeletionGroupFactory;
+ }
+
+ public List Generate(IEnumerable declarationDeletionTargets)
+ {
+ if (!declarationDeletionTargets.Any())
+ {
+ return new List();
+ }
+
+ var deletionGroupsModels = BuildDeletionGroupsGeneratorModels(declarationDeletionTargets);
+
+ var deletionGroups = new List();
+ foreach (var deletionGroupModel in deletionGroupsModels)
+ {
+ if (!deletionGroupModel.Targets?.Any() ?? true)
+ {
+ continue;
+ }
+
+ var generatedDeletionGroups = GenerateDeletionGroups(deletionGroupModel, _declarationDeletionGroupFactory);
+
+ deletionGroups.AddRange(generatedDeletionGroups.Where(dg => dg.Declarations.Any()));
+ }
+ return deletionGroups;
+ }
+
+ private static List BuildDeletionGroupsGeneratorModels(IEnumerable deleteDeclarationTargets)
+ {
+ var deletionGroupsModels = new List();
+
+ if (ContainsModuleScopeTargets(deleteDeclarationTargets, out var moduleScopeTargets))
+ {
+ var moduleScopeModel = new DeletionGroupsGeneratorModel()
+ {
+ Targets = moduleScopeTargets,
+ OrderedContexts = GetOrderedContextsForModuleScopedTargets(moduleScopeTargets.First()),
+ };
+
+ deletionGroupsModels.Add(moduleScopeModel);
+
+ deleteDeclarationTargets = deleteDeclarationTargets.Except(moduleScopeModel.Targets);
+ }
+
+ if (ContainsLocalScopeTargets(deleteDeclarationTargets, out var procedureScopeTargets))
+ {
+ var localScopeModels = CreateNonModuleScopedDeletionGroupModels(
+ procedureScopeTargets,
+ (t) => t.TargetProxy.ParentDeclaration.Context);
+
+ deletionGroupsModels.AddRange(localScopeModels);
+
+ deleteDeclarationTargets = deleteDeclarationTargets.Except(procedureScopeTargets);
+ }
+
+ if (ContainsUnterminatedBlockScopeTargets(deleteDeclarationTargets, out var unterminatedBlockScopeTargets))
+ {
+ var unterminatedBlockScopeModels = CreateNonModuleScopedDeletionGroupModels(
+ unterminatedBlockScopeTargets,
+ (t) => t.TargetContext.Parent as ParserRuleContext);
+
+ deletionGroupsModels.AddRange(unterminatedBlockScopeModels);
+
+ deleteDeclarationTargets = deleteDeclarationTargets.Except(unterminatedBlockScopeTargets);
+ }
+
+ if (ContainsEnumerationMembers(deleteDeclarationTargets, out var enumMembers))
+ {
+ var enumerationMembersModels = CreateNonModuleScopedDeletionGroupModels(
+ enumMembers,
+ (t) => t.TargetProxy.ParentDeclaration.Context,
+ "At least one EnumerationMember must be retained");
+
+ deletionGroupsModels.AddRange(enumerationMembersModels);
+
+ deleteDeclarationTargets = deleteDeclarationTargets.Except(enumMembers);
+ }
+
+ if (ContainsUDTMembers(deleteDeclarationTargets, out var udtMembers))
+ {
+ var userDefinedTypeMemberModels = CreateNonModuleScopedDeletionGroupModels(
+ udtMembers,
+ (t) => t.TargetProxy.ParentDeclaration.Context,
+ "At least one UserDefinedTypeMember must be retained");
+
+ deletionGroupsModels.AddRange(userDefinedTypeMemberModels);
+
+ deleteDeclarationTargets = deleteDeclarationTargets.Except(udtMembers);
+ }
+
+ if (deleteDeclarationTargets.Any())
+ {
+ throw new InvalidOperationException($"Encountered Unhandled Target:{deleteDeclarationTargets.First().TargetProxy.DeclarationType}({deleteDeclarationTargets.First().TargetProxy.IdentifierName})");
+ }
+
+ return deletionGroupsModels;
+ }
+
+ private static List GenerateDeletionGroups(DeletionGroupsGeneratorModel model, IDeclarationDeletionGroupFactory declarationDeletionGroupFactory)
+ {
+ var orderedContexts = model.OrderedContexts.ToList();
+
+ var nonDeleteIndices = orderedContexts.Where(c => !model.Targets.Any(d => d.TargetContext == c))
+ .Select(c => orderedContexts.IndexOf(c)).OrderBy(t => t).ToList();
+
+ //If there are zero nonDeleteIndices, the request is to Delete all the declarations in
+ //the scope -> return a single DeletionGroup
+ if (!nonDeleteIndices.Any())
+ {
+ if (model.AllScopedTargetsDeletedExceptionMessage != null)
+ {
+ //Deleting all EnumerationMembers of an Enumeration or all UserDefinedTypeMembers of a UserDefinedType
+ //results in uncompilable code. It is an exception here because this use case should have been handled upstream.
+ throw new InvalidOperationException(model.AllScopedTargetsDeletedExceptionMessage);
+ }
+
+ return new List()
+ {
+ declarationDeletionGroupFactory.Create(model.Targets.OrderBy(t => t.TargetContext.GetSelection()))
+ };
+ }
+
+ var deletionGroups = new List();
+
+ foreach ((int? Start, int? End) indexPair in NonDeleteIndicePairGenerator.Generate(nonDeleteIndices))
+ {
+ if (!(indexPair.Start.HasValue || indexPair.End.HasValue))
+ {
+ continue;
+ }
+
+ var groupedTargets = GetOrderedDeleteTargets(model, (indexPair.Start, indexPair.End));
+
+ var deletionGroup = declarationDeletionGroupFactory.Create(groupedTargets);
+
+ deletionGroup.PrecedingNonDeletedContext = indexPair.Start.HasValue
+ ? orderedContexts.ElementAt(indexPair.Start.Value)
+ : null;
+
+ deletionGroups.Add(deletionGroup);
+ }
+
+ return deletionGroups;
+ }
+
+ private static IOrderedEnumerable GetOrderedDeleteTargets(DeletionGroupsGeneratorModel model, (int? Start, int? End) nonDeletePair)
+ {
+ var deleteContexts = new List();
+
+ //DeletionTargets occupy the first 1 to n contexts preceding the first retained context in scope
+ if (!nonDeletePair.Start.HasValue)
+ {
+ deleteContexts = model.OrderedContexts.Take(nonDeletePair.End.Value).ToList();
+ }
+ //DeletionTargets occupy the last 1 to n contexts after the last retained context in scope
+ else if (!nonDeletePair.End.HasValue)
+ {
+ deleteContexts = model.OrderedContexts.Skip(nonDeletePair.Start.Value + 1).ToList();
+ }
+ //A group of delete target contexts are bounded by a pair of retained contexts in scope
+ else
+ {
+ deleteContexts = model.OrderedContexts
+ .Skip(nonDeletePair.Start.Value + 1)
+ .Take(nonDeletePair.End.Value - nonDeletePair.Start.Value - 1)
+ .ToList();
+ }
+
+ return model.Targets.Where(t => deleteContexts
+ .Contains(t.TargetContext))
+ .OrderBy(rt => rt.TargetContext.GetSelection());
+ }
+
+ private static IEnumerable CreateNonModuleScopedDeletionGroupModels(IEnumerable targets, Func getOrganizingContext, string allScopedTargetsDeletedExceptionMessage = null)
+ {
+ var deletionGroupModels = new List();
+
+ foreach (var targetGroup in targets.ToLookup(t => getOrganizingContext(t)))
+ {
+ var deletionGroupModel = new DeletionGroupsGeneratorModel()
+ {
+ Targets = targetGroup.ToList(),
+ OrderedContexts = GetOrderedContextsForNonModuleScopedTargets(targetGroup.First()),
+ AllScopedTargetsDeletedExceptionMessage = allScopedTargetsDeletedExceptionMessage
+ };
+
+ deletionGroupModels.Add(deletionGroupModel);
+ }
+
+ return deletionGroupModels;
+ }
+
+ private static bool ContainsModuleScopeTargets(IEnumerable deleteDeclarationTargets, out List moduleScopeTargets)
+ {
+ moduleScopeTargets = deleteDeclarationTargets.Where(t => t.TargetProxy.ParentDeclaration is ModuleDeclaration).ToList();
+ return moduleScopeTargets.Any();
+ }
+
+ private static bool ContainsLocalScopeTargets(IEnumerable deleteDeclarationTargets, out List procedureScopeTargets)
+ {
+ procedureScopeTargets = deleteDeclarationTargets.Where(t => t.TargetProxy.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Member)
+ && !(t.TargetContext.Parent is VBAParser.UnterminatedBlockContext)).ToList();
+
+ return procedureScopeTargets.Any();
+ }
+
+ private static bool ContainsUnterminatedBlockScopeTargets(IEnumerable deleteDeclarationTargets, out List unterminatedBlockScopeTargets)
+ {
+ unterminatedBlockScopeTargets = deleteDeclarationTargets.Where(t => t.TargetProxy.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Member)
+ && t.TargetContext.Parent is VBAParser.UnterminatedBlockContext).ToList();
+
+ return unterminatedBlockScopeTargets.Any();
+ }
+
+ private static bool ContainsUDTMembers(IEnumerable deleteDeclarationTargets, out List udtMembers)
+ {
+ udtMembers = deleteDeclarationTargets.Where(t => t.TargetProxy.DeclarationType.HasFlag(DeclarationType.UserDefinedTypeMember)).ToList();
+ return udtMembers.Any();
+ }
+
+ private static bool ContainsEnumerationMembers(IEnumerable deleteDeclarationTargets, out List enumMembers)
+ {
+ enumMembers = deleteDeclarationTargets.Where(t => t.TargetProxy.DeclarationType.HasFlag(DeclarationType.EnumerationMember)).ToList();
+ return enumMembers.Any();
+ }
+
+ private static IOrderedEnumerable GetOrderedContextsForNonModuleScopedTargets(IDeclarationDeletionTarget deleteTarget)
+ {
+ if (deleteTarget is ILocalScopeDeletionTarget localTarget)
+ {
+ return localTarget.ScopingContext.GetChildrenOfType()
+ .OrderBy(e => e?.GetSelection());
+ }
+
+ var contexts = deleteTarget.TargetProxy.DeclarationType == DeclarationType.EnumerationMember
+ ? deleteTarget.TargetProxy.Context.GetAncestor().GetChildrenOfType()
+ : deleteTarget.TargetProxy.Context.GetAncestor().GetChildrenOfType();
+
+ return contexts.OrderBy(e => e?.GetSelection());
+ }
+
+ private static IOrderedEnumerable GetOrderedContextsForModuleScopedTargets(IDeclarationDeletionTarget deleteTarget)
+ {
+ var moduleContext = deleteTarget.TargetProxy.Context.GetAncestor();
+
+ var moduleDeclarationElements = moduleContext.GetChild()
+ .GetChildrenOfType();
+
+ var moduleBodyElements = moduleContext.GetChild()
+ .GetChildrenOfType();
+
+ return moduleDeclarationElements.Concat(moduleBodyElements)
+ .OrderBy(c => c?.GetSelection());
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/NonDeleteIndicePairGenerator.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/NonDeleteIndicePairGenerator.cs
new file mode 100644
index 0000000000..797a3086a0
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionGroups/NonDeleteIndicePairGenerator.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ internal class NonDeleteIndicePairGenerator
+ {
+ ///
+ /// Given a list of non-negative integers, generates a list of Nullable<int> pairs
+ /// that are the bounding values of missing integer values.
+ /// e.g., 1,2,4,5,6,9 generates (null, 1), (2, 4), (6, 9), (9, null)
+ ///
+ ///
+ /// A null first value implies all indices up to the second value of the pair.
+ /// A null second value implies all indices following the first value of the pair.
+ ///
+ public static List<(int?, int?)> Generate(List nonDeleteIndices)
+ {
+ var results = new List<(int?, int?)>();
+
+ if (!nonDeleteIndices.Any())
+ {
+ return results;
+ }
+
+ var startingNonDeleteContextIndex = nonDeleteIndices.ElementAt(0);
+ if (startingNonDeleteContextIndex != 0)
+ {
+ results.Add((null, startingNonDeleteContextIndex));
+ }
+
+ for (var nonDeleteIndex = 0; nonDeleteIndex + 1 < nonDeleteIndices.Count; nonDeleteIndex++)
+ {
+ var currentIndice = nonDeleteIndices.ElementAt(nonDeleteIndex);
+ var nextIndice = nonDeleteIndices.ElementAt(nonDeleteIndex + 1);
+ if (nonDeleteIndices.ElementAt(nonDeleteIndex + 1) - nonDeleteIndices.ElementAt(nonDeleteIndex) == 1)
+ {
+ continue;
+ }
+ results.Add((currentIndice, nextIndice));
+ }
+
+ results.Add((nonDeleteIndices.Last(), null));
+ return results;
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/DeclarationDeletionTargetBase.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/DeclarationDeletionTargetBase.cs
new file mode 100644
index 0000000000..dfd497dd73
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/DeclarationDeletionTargetBase.cs
@@ -0,0 +1,198 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ public abstract class DeclarationDeletionTargetBase : IComparable, IComparable
+ {
+ private readonly IDeclarationFinderProvider _declarationFinderProvider;
+ private readonly HashSet _targets;
+ private readonly IModuleRewriter _rewriter;
+
+ private List _allDeclarationsInListContext;
+
+ public DeclarationDeletionTargetBase(IDeclarationFinderProvider declarationFinderProvider, Declaration target, IModuleRewriter rewriter)
+ {
+ if (target is null || declarationFinderProvider is null || rewriter is null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ _declarationFinderProvider = declarationFinderProvider;
+ _targets = new HashSet()
+ {
+ target
+ };
+
+ _rewriter = rewriter;
+ }
+
+ protected HashSet Targets => _targets;
+
+ protected IDeclarationFinderProvider DeclarationFinderProvider => _declarationFinderProvider;
+
+ public IModuleRewriter Rewriter => _rewriter;
+
+ public virtual bool IsFullDelete => true;
+
+ public void AddTargets(IEnumerable targets)
+ {
+ if (ListContext != null && targets.Any(t => GetListContext(t) != ListContext))
+ {
+ throw new ArgumentException("Attempted to add a delete target from a different list context");
+ }
+
+ foreach (var t in targets)
+ {
+ _targets.Add(t);
+ }
+ }
+
+ public Declaration TargetProxy => _targets.First();
+
+ public IReadOnlyCollection Declarations => _targets.ToList();
+
+ //e.g., ModuleBodyElementContext, ModuleDeclarationElementContext, BlockContext
+ public ParserRuleContext TargetContext { protected set; get; }
+
+ public IReadOnlyList AllDeclarationsInListContext
+ {
+ get
+ {
+ if (_allDeclarationsInListContext is null)
+ {
+ _allDeclarationsInListContext = ListContext != null
+ ? _declarationFinderProvider.DeclarationFinder.UserDeclarations(TargetProxy.DeclarationType)
+ .Where(d => GetListContext(d) == ListContext)
+ .Select(d => d)
+ .ToList()
+ : new List() { TargetProxy };
+ }
+ return _allDeclarationsInListContext;
+ }
+ }
+
+ public IEnumerable RetainedDeclarations
+ => AllDeclarationsInListContext.Except(_targets).ToList();
+
+ public virtual VBAParser.EndOfStatementContext PrecedingEOSContext { set; get; }
+
+ public VBAParser.EndOfStatementContext TargetEOSContext { protected set; get; }
+
+ public VBAParser.EndOfStatementContext EOSContextToReplace => PrecedingEOSContext ?? TargetEOSContext;
+
+ public virtual ParserRuleContext DeleteContext { protected set; get; }
+
+ public ParserRuleContext ListContext { protected set; get; }
+
+ public string ModifiedTargetEOSContent =>
+ TargetEOSContext != null
+ ? TargetEOSContext.CurrentContent(Rewriter)
+ : string.Empty;
+
+ private string CurrentPrecedingEOSContent =>
+ PrecedingEOSContext != null
+ ? PrecedingEOSContext.CurrentContent(Rewriter)
+ : string.Empty;
+
+ private static string RemoveStartingNewLines(string content)
+ => string.Concat(content.SkipWhile(c => c == '\r' || c == '\n'));
+
+ public virtual string BuildEOSReplacementContent()
+ {
+ string body;
+ if (ModifiedTargetEOSContent.Contains(Tokens.CommentMarker))
+ {
+ if (CurrentPrecedingEOSContent.Contains(Tokens.CommentMarker))
+ {
+ body = GetCurrentTextPriorToSeparationAndIndentation(PrecedingEOSContext, Rewriter)
+ + GetCurrentTextPriorToSeparationAndIndentation(TargetEOSContext, Rewriter);
+ }
+ else
+ {
+ var eosContentPriorToSeparationAndIndentation = GetCurrentTextPriorToSeparationAndIndentation(TargetEOSContext, Rewriter);
+
+ body = GetCurrentTextPriorToSeparationAndIndentation(PrecedingEOSContext, Rewriter)
+ + PrecedingEOSSeparation
+ + RemoveStartingNewLines(eosContentPriorToSeparationAndIndentation);
+ }
+
+ return body + EOSSeparation + EOSIndentation;
+ }
+
+ body = GetCurrentTextPriorToSeparationAndIndentation(PrecedingEOSContext, Rewriter);
+
+ return body + PrecedingEOSSeparation + EOSIndentation;
+ }
+
+ protected string PrecedingEOSSeparation => PrecedingEOSContext.GetSeparation();
+
+ public string EOSSeparation => TargetEOSContext.GetSeparation();
+
+ public string EOSIndentation => TargetEOSContext.GetIndentation();
+
+ public virtual bool DeletionIncludesEOSContext { protected set; get; } = true;
+
+ public VBAParser.CommentContext GetDeclarationLogicalLineCommentContext()
+ {
+ var individualNonEOFEOS = TargetEOSContext?.individualNonEOFEndOfStatement().FirstOrDefault();
+
+ return individualNonEOFEOS?.GetDescendent();
+ }
+
+ protected static ParserRuleContext GetListContext(Declaration target)
+ {
+ switch (target.DeclarationType)
+ {
+ case DeclarationType.Variable:
+ return target.Context.GetAncestor();
+ case DeclarationType.Constant:
+ return target.Context.GetAncestor();
+ default:
+ return null;
+ }
+ }
+
+ protected static string GetCurrentTextPriorToSeparationAndIndentation(VBAParser.EndOfStatementContext eosContext, IModuleRewriter rewriter)
+ {
+ var eosContent = eosContext.GetSeparationAndIndentationContent();
+ var modifiedContent = eosContext.CurrentContent(rewriter);
+
+ return modifiedContent.EndsWith(eosContent)
+ ? modifiedContent.Substring(0, modifiedContent.Length - eosContent.Length)
+ : modifiedContent;
+ }
+
+ public int CompareTo(DeclarationDeletionTargetBase other)
+ {
+ return other is null ? -1 : CompareTo(other);
+ }
+
+ public int CompareTo(object obj)
+ {
+
+ if (obj != null && !(obj is DeclarationDeletionTargetBase))
+ {
+ throw new ArgumentException("Object must be of type DeclarationDeletionTargetBase.");
+ }
+
+ var other = obj as DeclarationDeletionTargetBase;
+ var thisFirstDeclaration = Declarations.OrderBy(d => d.Selection).FirstOrDefault();
+ var otherFirstDeclaration = other.Declarations.OrderBy(d => d.Selection).FirstOrDefault();
+
+ if (thisFirstDeclaration.Selection < otherFirstDeclaration.Selection)
+ {
+ return -1;
+ }
+
+ return thisFirstDeclaration.Selection == otherFirstDeclaration.Selection ? 0 : 1;
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/DeclarationDeletionTargetFactory.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/DeclarationDeletionTargetFactory.cs
new file mode 100644
index 0000000000..01915060c4
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/DeclarationDeletionTargetFactory.cs
@@ -0,0 +1,66 @@
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using Rubberduck.Refactorings.DeleteDeclarations;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Rubberduck.Refactorings
+{
+ public class DeclarationDeletionTargetFactory : IDeclarationDeletionTargetFactory
+ {
+ private readonly IDeclarationFinderProvider _declarationFinderProvider;
+
+ public DeclarationDeletionTargetFactory(IDeclarationFinderProvider declarationFinderProvider)
+ {
+ _declarationFinderProvider = declarationFinderProvider;
+ }
+
+ public IEnumerable CreateMany(IEnumerable declarations, IRewriteSession rewriteSession)
+ {
+ return declarations.Select(d => Create(d, rewriteSession));
+ }
+
+ public IDeclarationDeletionTarget Create(Declaration declaration, IRewriteSession rewriteSession)
+ {
+ var rewriter = rewriteSession.CheckOutModuleRewriter(declaration.QualifiedModuleName);
+
+ if (declaration.DeclarationType.HasFlag(DeclarationType.Member))
+ {
+ return declaration.DeclarationType.HasFlag(DeclarationType.Property)
+ ? new PropertyMemberDeletionTarget(_declarationFinderProvider, declaration, rewriter)
+ : new ModuleElementDeletionTarget(_declarationFinderProvider, declaration, rewriter);
+ }
+
+ switch (declaration.DeclarationType)
+ {
+ case DeclarationType.UserDefinedTypeMember:
+ return new UdtMemberDeletionTarget(_declarationFinderProvider, declaration, rewriter);
+
+ case DeclarationType.EnumerationMember:
+ return new EnumMemberDeletionTarget(_declarationFinderProvider, declaration, rewriter);
+
+ case DeclarationType.Variable:
+ return declaration.ParentDeclaration is ModuleDeclaration
+ ? new ModuleElementDeletionTarget(_declarationFinderProvider, declaration, rewriter)
+ : new ProcedureLocalDeletionTarget(_declarationFinderProvider, declaration, rewriter) as IDeclarationDeletionTarget;
+
+ case DeclarationType.Constant:
+ return declaration.ParentDeclaration is ModuleDeclaration
+ ? new ModuleElementDeletionTarget(_declarationFinderProvider, declaration, rewriter)
+ : new ProcedureLocalDeletionTarget(_declarationFinderProvider, declaration, rewriter) as IDeclarationDeletionTarget;
+
+ case DeclarationType.Enumeration:
+ case DeclarationType.UserDefinedType:
+ return new ModuleElementDeletionTarget(_declarationFinderProvider, declaration, rewriter);
+
+ case DeclarationType.LineLabel:
+ return new LineLabelDeletionTarget(_declarationFinderProvider, declaration, rewriter);
+ default:
+ throw new ArgumentException();
+ }
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/EnumMemberDeletionTarget.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/EnumMemberDeletionTarget.cs
new file mode 100644
index 0000000000..a66fa36ee5
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/EnumMemberDeletionTarget.cs
@@ -0,0 +1,33 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using Rubberduck.Refactorings.DeleteDeclarations.Abstract;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ internal class EnumMemberDeletionTarget : DeclarationDeletionTargetBase, IEnumMemberDeletionTarget
+ {
+ public EnumMemberDeletionTarget(IDeclarationFinderProvider declarationFinderProvider, Declaration target, IModuleRewriter rewriter)
+ : base(declarationFinderProvider, target, rewriter)
+ {
+ ListContext = target.Context.GetAncestor();
+
+ TargetContext = target.Context;
+
+ DeleteContext = target.Context.GetChild();
+
+ PrecedingEOSContext = TargetProxy.Context == ListContext.children.SkipWhile(ch => !(ch is VBAParser.EnumerationStmt_ConstantContext)).First()
+ ? ListContext.GetChild()
+ : (ListContext.children
+ .TakeWhile(ch => ch != TargetProxy.Context)
+ .Last() as ParserRuleContext)
+ .GetChild();
+
+ TargetEOSContext = DeleteContext.GetFollowingEndOfStatementContext();
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/LineLabelDeletionTarget.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/LineLabelDeletionTarget.cs
new file mode 100644
index 0000000000..4fd3cbecdc
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/LineLabelDeletionTarget.cs
@@ -0,0 +1,70 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ internal class LineLabelDeletionTarget : ProcedureLocalDeletionTarget, ILabelDeletionTarget
+ {
+ public LineLabelDeletionTarget(IDeclarationFinderProvider declarationFinderProvider, Declaration target, IModuleRewriter rewriter)
+ : base(declarationFinderProvider, target, rewriter)
+ {
+ ListContext = null;
+
+ DeleteContext = target.Context.GetAncestor();
+
+ TargetEOSContext = TargetContext.GetFollowingEndOfStatementContext();
+
+ if (TargetContext.TryGetChildContext(out _))
+ {
+ //There is a declaration on the same logical line - delete just the label
+ //Note: ProcedureLocalDeletionTarget handles cases where a Label AND a declaration on the same logical line are to be deleted
+ DeleteContext = target.Context;
+ TargetEOSContext = null;
+ }
+ }
+
+ public override bool IsLabel(out ILabelDeletionTarget labelTarget)
+ {
+ labelTarget = this;
+ return true;
+ }
+
+ public override ParserRuleContext DeleteContext { protected set; get; }
+
+ public bool HasSameLogicalLineListContext(out ParserRuleContext varOrConst)
+ {
+ varOrConst = null;
+ if (TargetContext.TryGetChildContext(out var relatedVarOrConst))
+ {
+ if (relatedVarOrConst.TryGetChildContext(out var constStmtContext))
+ {
+ varOrConst = constStmtContext;
+ }
+ else if (relatedVarOrConst.TryGetChildContext(out var varStmtContext))
+ {
+ varOrConst = varStmtContext.GetChild();
+ }
+ }
+ return varOrConst != null;
+ }
+
+ public override bool HasSameLogicalLineLabel(out VBAParser.StatementLabelDefinitionContext labelContext)
+ {
+ labelContext = null;
+ return false;
+ }
+
+ public bool ReplaceLabelWithWhitespace { set; get; }
+
+ public bool HasFollowingMainBlockStatementContext(out VBAParser.MainBlockStmtContext mainBlockStmtContext)
+ {
+ mainBlockStmtContext = TargetContext.GetDescendent();
+ return mainBlockStmtContext != null;
+ }
+
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/ModuleElementDeletionTarget.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/ModuleElementDeletionTarget.cs
new file mode 100644
index 0000000000..22efabdce5
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/ModuleElementDeletionTarget.cs
@@ -0,0 +1,42 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ internal class ModuleElementDeletionTarget : DeclarationDeletionTargetBase, IModuleElementDeletionTarget
+ {
+ public ModuleElementDeletionTarget(IDeclarationFinderProvider declarationFinderProvider, Declaration target, IModuleRewriter rewriter)
+ : base(declarationFinderProvider, target, rewriter)
+ {
+ ListContext = GetListContext(target);
+
+ if (target.Context.TryGetAncestor(out var mde))
+ {
+ TargetContext = mde;
+ }
+ else if (target.Context.TryGetAncestor(out var mbe))
+ {
+ TargetContext = mbe;
+ }
+
+ DeleteContext = target.DeclarationType.HasFlag(DeclarationType.Member)
+ ? target.Context.GetAncestor()
+ : target.Context.GetAncestor() as ParserRuleContext;
+
+ //The preceding EOS Context cannot be determined directly from the target. It depends upon what else is deleted
+ //adjacent to the target.
+ PrecedingEOSContext = null;
+
+ TargetEOSContext = DeleteContext.GetFollowingEndOfStatementContext();
+ }
+
+ public override bool IsFullDelete
+ => TargetProxy.DeclarationType != DeclarationType.Variable && TargetProxy.DeclarationType != DeclarationType.Constant
+ || AllDeclarationsInListContext.Intersect(Targets).Count() == AllDeclarationsInListContext.Count;
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/ProcedureLocalDeletionTarget.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/ProcedureLocalDeletionTarget.cs
new file mode 100644
index 0000000000..4406fa816c
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/ProcedureLocalDeletionTarget.cs
@@ -0,0 +1,105 @@
+using Antlr4.Runtime;
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ internal class ProcedureLocalDeletionTarget : DeclarationDeletionTargetBase, ILocalScopeDeletionTarget where T : ParserRuleContext
+ {
+ public ProcedureLocalDeletionTarget(IDeclarationFinderProvider declarationFinderProvider, Declaration target, IModuleRewriter rewriter)
+ : base(declarationFinderProvider, target, rewriter)
+ {
+ ListContext = target.Context.GetAncestor();
+
+ TargetContext = target.Context.GetAncestor();
+
+ //If there is a label on the declaration's line, delete just the declaration's context
+ DeleteContext = TargetContext.TryGetChildContext(out _)
+ ? TargetContext.GetChild()
+ : target.Context.GetAncestor() as ParserRuleContext;
+
+ //The preceding EOS Context cannot be determined directly from the target. It depends upon
+ //what other targets are deleted adjacent to the target.
+ PrecedingEOSContext = null;
+
+ TargetEOSContext = DeleteContext.GetFollowingEndOfStatementContext();
+
+ switch (TargetContext.Parent.Parent)
+ {
+ case VBAParser.ForNextStmtContext forNext:
+ ScopingContext = forNext.GetChild();
+ break;
+ case VBAParser.ForEachStmtContext forEach:
+ ScopingContext = forEach.GetChild();
+ break;
+ default:
+ ScopingContext = TargetContext.Parent as ParserRuleContext;
+ break;
+ }
+
+ //Initializes for the default use case where a Label exists on the same logical line as a Variable/Constant to be
+ //deleted. The VBAParser.EndOfStatementContext (of the Variable/Const) is retained to provide spacing
+ //and indentation for the next BlockStatementContext. If the Label is also to be deleted, this flag is modified.
+ DeletionIncludesEOSContext = !HasSameLogicalLineLabel(out _);
+ }
+
+ public override bool IsFullDelete
+ => AllDeclarationsInListContext.Intersect(Targets).Count() == AllDeclarationsInListContext.Count;
+
+ public ILocalScopeDeletionTarget AssociatedLabelToDelete { private set; get; }
+
+ public void SetupToDeleteAssociatedLabel(ILabelDeletionTarget label)
+ {
+ AssociatedLabelToDelete = label as ILocalScopeDeletionTarget;
+ DeletionIncludesEOSContext = label != null;
+ }
+
+ public virtual bool IsLabel(out ILabelDeletionTarget labelTarget)
+ {
+ labelTarget = null;
+ return false;
+ }
+
+ public ParserRuleContext ScopingContext { get; }
+
+ public override ParserRuleContext DeleteContext => AssociatedLabelToDelete != null
+ ? TargetContext
+ : TargetContext.GetChild();
+
+ public virtual bool HasSameLogicalLineLabel(out VBAParser.StatementLabelDefinitionContext labelContext)
+ {
+ return TargetContext.TryGetChildContext(out labelContext);
+ }
+
+ public override string BuildEOSReplacementContent()
+ {
+ if (!(DeleteContext.Parent is ParserRuleContext prc
+ && prc.TryGetChildContext(out _)))
+ {
+ //No label to contend with
+ return base.BuildEOSReplacementContent();
+ }
+
+ var replacement = string.Empty;
+ var separationAndIndentation = string.Empty;
+
+ if (!ModifiedTargetEOSContent.Contains(Tokens.CommentMarker))
+ {
+ var priorToSeparationContent = GetCurrentTextPriorToSeparationAndIndentation(PrecedingEOSContext, Rewriter);
+
+ if (priorToSeparationContent.Contains(Tokens.CommentMarker))
+ {
+ replacement = priorToSeparationContent;
+ }
+
+ separationAndIndentation = PrecedingEOSContext.GetSeparation();
+ }
+
+ return replacement + separationAndIndentation;
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/PropertyMemberDeletionTarget.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/PropertyMemberDeletionTarget.cs
new file mode 100644
index 0000000000..0a7430db12
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/PropertyMemberDeletionTarget.cs
@@ -0,0 +1,81 @@
+using System.Collections.Generic;
+using System.Linq;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ internal class PropertyMemberDeletionTarget : ModuleElementDeletionTarget, IPropertyDeletionTarget
+ {
+ public PropertyMemberDeletionTarget(IDeclarationFinderProvider declarationFinderProvider, Declaration target, IModuleRewriter rewriter)
+ : base(declarationFinderProvider, target, rewriter)
+ { }
+
+ public override string BuildEOSReplacementContent()
+ {
+ if (ModifiedTargetEOSContent.Contains(Tokens.CommentMarker))
+ {
+ return base.BuildEOSReplacementContent();
+ }
+
+ var body = GetCurrentTextPriorToSeparationAndIndentation(PrecedingEOSContext, Rewriter);
+
+ var ending = IsGroupedWithRelatedProperties() && IsLastPropertyOfGroup()
+ ? $"{EOSSeparation}{EOSIndentation}"
+ : $"{PrecedingEOSSeparation}{EOSIndentation}";
+
+ return body + ending;
+ }
+
+ public bool IsGroupedWithRelatedProperties()
+ {
+ var propertiesOfSameName = DeclarationFinderProvider.DeclarationFinder
+ .MatchName(TargetProxy.IdentifierName)
+ .ToList();
+
+ if (propertiesOfSameName?.Count == 1)
+ {
+ return false;
+ }
+
+ var orderedPropertiesOfSameName = propertiesOfSameName
+ .OrderBy(d => d.Selection)
+ .ToList();
+
+ var allOtherMembers = DeclarationFinderProvider.DeclarationFinder
+ .Members(TargetProxy.QualifiedModuleName)
+ .Where(d => d.DeclarationType.HasFlag(DeclarationType.Member) && !propertiesOfSameName.Contains(d));
+
+ bool DeclarationsAreAdjacent(Declaration first, Declaration next)
+ => !allOtherMembers.Any(proc => proc.Selection > first.Selection && proc.Selection < next.Selection);
+
+ var grouped = new List();
+
+ for (var position = 0; position + 1 < orderedPropertiesOfSameName.Count; position++)
+ {
+ var first = orderedPropertiesOfSameName.ElementAt(position);
+ var second = orderedPropertiesOfSameName.ElementAt(position + 1);
+
+ if ((first == TargetProxy || second == TargetProxy) && DeclarationsAreAdjacent(first, second))
+ {
+ grouped.Add(first);
+ grouped.Add(second);
+ }
+ }
+
+ grouped.Distinct();
+ return grouped.Count > 1;
+ }
+
+ private bool IsLastPropertyOfGroup()
+ {
+ var lastPropertyAcessorDeclaration = DeclarationFinderProvider.DeclarationFinder
+ .MatchName(TargetProxy.IdentifierName)
+ .OrderBy(d => d.Selection).LastOrDefault();
+
+ return lastPropertyAcessorDeclaration == TargetProxy;
+ }
+ }
+}
diff --git a/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/UdtMemberDeletionTarget.cs b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/UdtMemberDeletionTarget.cs
new file mode 100644
index 0000000000..f00f4caae1
--- /dev/null
+++ b/Rubberduck.Refactorings/DeleteDeclarations/DeletionTargets/UdtMemberDeletionTarget.cs
@@ -0,0 +1,30 @@
+using Rubberduck.Parsing;
+using Rubberduck.Parsing.Grammar;
+using Rubberduck.Parsing.Rewriter;
+using Rubberduck.Parsing.Symbols;
+using Rubberduck.Parsing.VBA;
+using System.Linq;
+
+namespace Rubberduck.Refactorings.DeleteDeclarations
+{
+ internal class UdtMemberDeletionTarget : DeclarationDeletionTargetBase, IUdtMemberDeletionTarget
+ {
+ public UdtMemberDeletionTarget(IDeclarationFinderProvider declarationFinderProvider, Declaration target, IModuleRewriter rewriter)
+ : base(declarationFinderProvider, target, rewriter)
+ {
+ ListContext = target.Context.GetAncestor();
+
+ TargetContext = target.Context;
+
+ DeleteContext = target.Context;
+
+ PrecedingEOSContext = DeleteContext == ListContext.children.First()
+ ? TargetProxy.Context.GetAncestor()
+ .GetChild()
+ : ListContext.children.TakeWhile(ch => ch != TargetProxy.Context).Last() as VBAParser.EndOfStatementContext;
+
+ TargetEOSContext = DeleteContext.GetFollowingEndOfStatementContext();
+ }
+ }
+}
+
diff --git a/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldRefactoringActionsProvider.cs b/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldRefactoringActionsProvider.cs
index aa86f30122..e51a540d5d 100644
--- a/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldRefactoringActionsProvider.cs
+++ b/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldRefactoringActionsProvider.cs
@@ -1,6 +1,7 @@
using Rubberduck.Refactorings.ReplaceDeclarationIdentifier;
using Rubberduck.Refactorings.EncapsulateFieldInsertNewCode;
using Rubberduck.Refactorings.ModifyUserDefinedType;
+using Rubberduck.Refactorings.DeleteDeclarations;
namespace Rubberduck.Refactorings.EncapsulateField
{
@@ -9,6 +10,7 @@ public interface IEncapsulateFieldRefactoringActionsProvider
ICodeOnlyRefactoringAction ReplaceDeclarationIdentifiers { get; }
ICodeOnlyRefactoringAction ModifyUserDefinedType { get; }
ICodeOnlyRefactoringAction EncapsulateFieldInsertNewCode { get; }
+ ICodeOnlyRefactoringAction DeleteDeclarations { get; }
}
///
@@ -20,15 +22,18 @@ public class EncapsulateFieldRefactoringActionsProvider : IEncapsulateFieldRefac
private readonly ReplaceDeclarationIdentifierRefactoringAction _replaceDeclarationIdentifiers;
private readonly ModifyUserDefinedTypeRefactoringAction _modifyUDTRefactoringAction;
private readonly EncapsulateFieldInsertNewCodeRefactoringAction _encapsulateFieldInsertNewCodeRefactoringAction;
+ private readonly DeleteDeclarationsRefactoringAction _deleteDeclarationRefactoringAction;
public EncapsulateFieldRefactoringActionsProvider(
ReplaceDeclarationIdentifierRefactoringAction replaceDeclarationIdentifierRefactoringAction,
ModifyUserDefinedTypeRefactoringAction modifyUserDefinedTypeRefactoringAction,
- EncapsulateFieldInsertNewCodeRefactoringAction encapsulateFieldInsertNewCodeRefactoringAction)
+ EncapsulateFieldInsertNewCodeRefactoringAction encapsulateFieldInsertNewCodeRefactoringAction,
+ DeleteDeclarationsRefactoringAction deleteDeclarationsRefactoringAction)
{
_replaceDeclarationIdentifiers = replaceDeclarationIdentifierRefactoringAction;
_modifyUDTRefactoringAction = modifyUserDefinedTypeRefactoringAction;
_encapsulateFieldInsertNewCodeRefactoringAction = encapsulateFieldInsertNewCodeRefactoringAction;
+ _deleteDeclarationRefactoringAction = deleteDeclarationsRefactoringAction;
}
public ICodeOnlyRefactoringAction ReplaceDeclarationIdentifiers
@@ -39,5 +44,8 @@ public ICodeOnlyRefactoringAction ModifyUserDefinedT
public ICodeOnlyRefactoringAction EncapsulateFieldInsertNewCode
=> _encapsulateFieldInsertNewCodeRefactoringAction;
+
+ public ICodeOnlyRefactoringAction DeleteDeclarations
+ => _deleteDeclarationRefactoringAction;
}
}
diff --git a/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldUseBackingField/EncapsulateFieldUseBackingFieldRefactoringAction.cs b/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldUseBackingField/EncapsulateFieldUseBackingFieldRefactoringAction.cs
index aa4da51fbb..5654351f0c 100644
--- a/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldUseBackingField/EncapsulateFieldUseBackingFieldRefactoringAction.cs
+++ b/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldUseBackingField/EncapsulateFieldUseBackingFieldRefactoringAction.cs
@@ -9,6 +9,7 @@
using System.Linq;
using Rubberduck.Refactorings.EncapsulateField;
using Rubberduck.Refactorings.EncapsulateFieldInsertNewCode;
+using Rubberduck.Refactorings.DeleteDeclarations;
namespace Rubberduck.Refactorings.EncapsulateFieldUseBackingField
{
@@ -16,6 +17,7 @@ public class EncapsulateFieldUseBackingFieldRefactoringAction : CodeOnlyRefactor
{
private readonly ICodeOnlyRefactoringAction _replaceDeclarationIdentifiers;
private readonly ICodeOnlyRefactoringAction _encapsulateFieldInsertNewCodeRefactoringAction;
+ private readonly ICodeOnlyRefactoringAction _deleteDeclarationsRefactoringAction;
private readonly INewContentAggregatorFactory _newContentAggregatorFactory;
private readonly IEncapsulateFieldReferenceReplacerFactory _encapsulateFieldReferenceReplacerFactory;
@@ -28,6 +30,7 @@ public EncapsulateFieldUseBackingFieldRefactoringAction(
{
_replaceDeclarationIdentifiers = refactoringActionsProvider.ReplaceDeclarationIdentifiers;
_encapsulateFieldInsertNewCodeRefactoringAction = refactoringActionsProvider.EncapsulateFieldInsertNewCode;
+ _deleteDeclarationsRefactoringAction = refactoringActionsProvider.DeleteDeclarations;
_newContentAggregatorFactory = newContentAggregatorFactory;
_encapsulateFieldReferenceReplacerFactory = encapsulateFieldReferenceReplacerFactory;
}
@@ -55,9 +58,9 @@ var publicFieldsDeclaredInListsToReDeclareAsPrivateBackingFields
private void ModifyFields(EncapsulateFieldUseBackingFieldModel model, List publicFieldsToRemove, IRewriteSession rewriteSession)
{
- var rewriter = rewriteSession.CheckOutModuleRewriter(model.QualifiedModuleName);
- rewriter.RemoveVariables(publicFieldsToRemove.Select(f => f.Declaration)
- .Cast());
+ var deletionsModel = new DeleteDeclarationsModel(publicFieldsToRemove.Select(f => f.Declaration));
+
+ _deleteDeclarationsRefactoringAction.Refactor(deletionsModel, rewriteSession);
var retainedFieldDeclarations = model.SelectedFieldCandidates
.Except(publicFieldsToRemove)
@@ -65,6 +68,8 @@ private void ModifyFields(EncapsulateFieldUseBackingFieldModel model, List
{
- private readonly ICodeOnlyRefactoringAction