diff --git a/Rubberduck.Refactorings/Common/CodeBuilder.cs b/Rubberduck.Refactorings/Common/CodeBuilder.cs new file mode 100644 index 0000000000..3e14a75c5b --- /dev/null +++ b/Rubberduck.Refactorings/Common/CodeBuilder.cs @@ -0,0 +1,284 @@ +using Rubberduck.Parsing.Grammar; +using Rubberduck.Parsing.Symbols; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Rubberduck.Refactorings +{ + public interface ICodeBuilder + { + /// + /// Returns ModuleBodyElementDeclaration signature with an ImprovedArgument list + /// + /// + /// + string ImprovedFullMemberSignature(ModuleBodyElementDeclaration declaration); + + /// + /// Returns a ModuleBodyElementDeclaration block + /// with an ImprovedArgument List + /// + /// + /// Main body content/logic of the member + /// + /// + /// + string BuildMemberBlockFromPrototype(ModuleBodyElementDeclaration declaration, + string content = null, + string accessibility = null, + string newIdentifier = null); + + /// + /// Returns the argument list for the input ModuleBodyElementDeclaration with the following improvements: + /// 1. Explicitly declares Property Let\Set value parameter as ByVal + /// 2. Ensures UserDefined Type parameters are declared either explicitly or implicitly as ByRef + /// + /// + /// + string ImprovedArgumentList(ModuleBodyElementDeclaration declaration); + + /// + /// Generates a Property Get codeblock based on the prototype declaration + /// + /// VariableDeclaration or UserDefinedTypeMember + /// + /// + /// Member body content. Formatting is the responsibility of the caller + /// Defaults to 'value' unless otherwise specified + /// + bool TryBuildPropertyGetCodeBlock(Declaration prototype, + string propertyIdentifier, + out string codeBlock, + string accessibility = null, + string content = null); + + /// + /// Generates a Property Let codeblock based on the prototype declaration + /// + /// VariableDeclaration or UserDefinedTypeMember + /// + /// + /// Membmer body content. Formatting is the responsibility of the caller + /// Defaults to 'value' unless otherwise specified + /// + bool TryBuildPropertyLetCodeBlock(Declaration prototype, + string propertyIdentifier, + out string codeBlock, + string accessibility = null, + string content = null, + string parameterIdentifier = null); + + /// + /// Generates a Property Set codeblock based on the prototype declaration + /// + /// VariableDeclaration or UserDefinedTypeMember + /// + /// + /// Membmer body content. Formatting is the responsibility of the caller + /// Defaults to 'value' unless otherwise specified + /// + bool TryBuildPropertySetCodeBlock(Declaration prototype, + string propertyIdentifier, + out string codeBlock, + string accessibility = null, + string content = null, + string parameterIdentifier = null); + } + + public class CodeBuilder : ICodeBuilder + { + public string BuildMemberBlockFromPrototype(ModuleBodyElementDeclaration declaration, + string content = null, + string accessibility = null, + string newIdentifier = null) + { + + var elements = new List() + { + ImprovedFullMemberSignatureInternal(declaration, accessibility, newIdentifier), + Environment.NewLine, + string.IsNullOrEmpty(content) ? null : $"{content}{Environment.NewLine}", + ProcedureEndStatement(declaration.DeclarationType), + Environment.NewLine, + }; + return string.Concat(elements); + } + + public bool TryBuildPropertyGetCodeBlock(Declaration prototype, string propertyIdentifier, out string codeBlock, string accessibility = null, string content = null) + => TryBuildPropertyBlockFromTarget(prototype, DeclarationType.PropertyGet, propertyIdentifier, out codeBlock, accessibility, content); + + public bool TryBuildPropertyLetCodeBlock(Declaration prototype, string propertyIdentifier, out string codeBlock, string accessibility = null, string content = null, string parameterIdentifier = null) + => TryBuildPropertyBlockFromTarget(prototype, DeclarationType.PropertyLet, propertyIdentifier, out codeBlock, accessibility, content, parameterIdentifier); + + public bool TryBuildPropertySetCodeBlock(Declaration prototype, string propertyIdentifier, out string codeBlock, string accessibility = null, string content = null, string parameterIdentifier = null) + => TryBuildPropertyBlockFromTarget(prototype, DeclarationType.PropertySet, propertyIdentifier, out codeBlock, accessibility, content, parameterIdentifier); + + private bool TryBuildPropertyBlockFromTarget(T prototype, DeclarationType letSetGetType, string propertyIdentifier, out string codeBlock, string accessibility = null, string content = null, string parameterIdentifier = null) where T : Declaration + { + codeBlock = string.Empty; + if (!letSetGetType.HasFlag(DeclarationType.Property)) + { + throw new ArgumentException(); + } + + if (!(prototype is VariableDeclaration || prototype.DeclarationType.HasFlag(DeclarationType.UserDefinedTypeMember))) + { + return false; + } + + var propertyValueParam = parameterIdentifier ?? Resources.RubberduckUI.EncapsulateField_DefaultPropertyParameter; + + var asType = prototype.IsArray + ? $"{Tokens.Variant}" + : IsEnumField(prototype) && prototype.AsTypeDeclaration.Accessibility.Equals(Accessibility.Private) + ? $"{Tokens.Long}" + : $"{prototype.AsTypeName}"; + + var asTypeClause = $"{Tokens.As} {asType}"; + + var paramMechanism = IsUserDefinedType(prototype) ? Tokens.ByRef : Tokens.ByVal; + + var letSetParamExpression = $"{paramMechanism} {propertyValueParam} {asTypeClause}"; + + codeBlock = letSetGetType.HasFlag(DeclarationType.PropertyGet) + ? string.Join(Environment.NewLine, $"{accessibility ?? Tokens.Public} {ProcedureTypeStatement(letSetGetType)} {propertyIdentifier}() {asTypeClause}", content, ProcedureEndStatement(letSetGetType)) + : string.Join(Environment.NewLine, $"{accessibility ?? Tokens.Public} {ProcedureTypeStatement(letSetGetType)} {propertyIdentifier}({letSetParamExpression})", content, ProcedureEndStatement(letSetGetType)); + return true; + } + + public string ImprovedFullMemberSignature(ModuleBodyElementDeclaration declaration) + => ImprovedFullMemberSignatureInternal(declaration); + + private string ImprovedFullMemberSignatureInternal(ModuleBodyElementDeclaration declaration, string accessibility = null, string newIdentifier = null) + { + var accessibilityToken = declaration.Accessibility.Equals(Accessibility.Implicit) + ? Tokens.Public + : $"{declaration.Accessibility.ToString()}"; + + var asTypeName = string.IsNullOrEmpty(declaration.AsTypeName) + ? string.Empty + : $" {Tokens.As} {declaration.AsTypeName}"; + + var elements = new List() + { + accessibility ?? accessibilityToken, + $" {ProcedureTypeStatement(declaration.DeclarationType)} ", + newIdentifier ?? declaration.IdentifierName, + $"({ImprovedArgumentList(declaration)})", + asTypeName + }; + return string.Concat(elements).Trim(); + + } + + public string ImprovedArgumentList(ModuleBodyElementDeclaration declaration) + { + var arguments = Enumerable.Empty(); + if (declaration is IParameterizedDeclaration parameterizedDeclaration) + { + arguments = parameterizedDeclaration.Parameters + .OrderBy(parameter => parameter.Selection) + .Select(parameter => BuildParameterDeclaration( + parameter, + parameter.Equals(parameterizedDeclaration.Parameters.LastOrDefault()) + && declaration.DeclarationType.HasFlag(DeclarationType.Property) + && !declaration.DeclarationType.Equals(DeclarationType.PropertyGet))); + } + return $"{string.Join(", ", arguments)}"; + } + + private static string BuildParameterDeclaration(ParameterDeclaration parameter, bool forceExplicitByValAccess) + { + var optionalParamType = parameter.IsParamArray + ? Tokens.ParamArray + : parameter.IsOptional ? Tokens.Optional : string.Empty; + + var paramMechanism = parameter.IsImplicitByRef + ? string.Empty + : parameter.IsByRef ? Tokens.ByRef : Tokens.ByVal; + + if (forceExplicitByValAccess + && (string.IsNullOrEmpty(paramMechanism) || paramMechanism.Equals(Tokens.ByRef)) + && !IsUserDefinedType(parameter)) + { + paramMechanism = Tokens.ByVal; + } + + var name = parameter.IsArray + ? $"{parameter.IdentifierName}()" + : parameter.IdentifierName; + + var paramDeclarationElements = new List() + { + FormatOptionalElement(optionalParamType), + FormatOptionalElement(paramMechanism), + $"{name} ", + FormatAsTypeName(parameter.AsTypeName), + FormatDefaultValue(parameter.DefaultValue) + }; + + return string.Concat(paramDeclarationElements).Trim(); + } + + private static string FormatOptionalElement(string element) + => string.IsNullOrEmpty(element) ? string.Empty : $"{element} "; + + private static string FormatAsTypeName(string AsTypeName) + => string.IsNullOrEmpty(AsTypeName) ? string.Empty : $"As {AsTypeName} "; + + private static string FormatDefaultValue(string DefaultValue) + => string.IsNullOrEmpty(DefaultValue) ? string.Empty : $"= {DefaultValue}"; + + private static string ProcedureEndStatement(DeclarationType declarationType) + { + switch (declarationType) + { + case DeclarationType.Function: + return $"{Tokens.End} {Tokens.Function}"; + case DeclarationType.Procedure: + return $"{Tokens.End} {Tokens.Sub}"; + case DeclarationType.PropertyGet: + case DeclarationType.PropertyLet: + case DeclarationType.PropertySet: + return $"{Tokens.End} {Tokens.Property}"; + default: + throw new ArgumentException(); + } + } + + private static string ProcedureTypeStatement(DeclarationType declarationType) + { + switch (declarationType) + { + case DeclarationType.Function: + return Tokens.Function; + case DeclarationType.Procedure: + return Tokens.Sub; + case DeclarationType.PropertyGet: + return $"{Tokens.Property} {Tokens.Get}"; + case DeclarationType.PropertyLet: + return $"{Tokens.Property} {Tokens.Let}"; + case DeclarationType.PropertySet: + return $"{Tokens.Property} {Tokens.Set}"; + default: + throw new ArgumentException(); + } + } + + private static bool IsEnumField(VariableDeclaration declaration) + => IsMemberVariable(declaration) + && (declaration.AsTypeDeclaration?.DeclarationType.Equals(DeclarationType.Enumeration) ?? false); + + private static bool IsEnumField(Declaration declaration) + => IsMemberVariable(declaration) + && (declaration.AsTypeDeclaration?.DeclarationType.Equals(DeclarationType.Enumeration) ?? false); + + private static bool IsUserDefinedType(Declaration declaration) + => (declaration.AsTypeDeclaration?.DeclarationType.Equals(DeclarationType.UserDefinedType) ?? false); + + private static bool IsMemberVariable(Declaration declaration) + => declaration.DeclarationType.HasFlag(DeclarationType.Variable) + && !declaration.ParentDeclaration.DeclarationType.HasFlag(DeclarationType.Member); + } +} diff --git a/Rubberduck.Refactorings/Common/DeclarationExtensions.cs b/Rubberduck.Refactorings/Common/DeclarationExtensions.cs index 4e9f7b24c2..2c8519b8e8 100644 --- a/Rubberduck.Refactorings/Common/DeclarationExtensions.cs +++ b/Rubberduck.Refactorings/Common/DeclarationExtensions.cs @@ -1,10 +1,6 @@ using Rubberduck.Parsing; using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Symbols; -using Rubberduck.Refactorings.Common; -using System; -using System.Collections.Generic; -using System.Linq; namespace Rubberduck.Refactorings.Common { @@ -45,62 +41,5 @@ public static bool IsDeclaredInList(this Declaration declaration) return declaration.Context.TryGetAncestor(out var varList) && varList.ChildCount > 1; } - - /// - /// Generates a Property Member code block specified by the letSetGet DeclarationType argument. - /// - /// - /// - /// - /// - /// - /// - /// - public static string FieldToPropertyBlock(this Declaration variable, DeclarationType letSetGetType, string propertyIdentifier, string accessibility = null, string content = null, string parameterIdentifier = null) - { - //"value" is the default - var propertyValueParam = parameterIdentifier ?? Resources.RubberduckUI.EncapsulateField_DefaultPropertyParameter; - - var propertyEndStmt = $"{Tokens.End} {Tokens.Property}"; - - var asType = variable.IsArray - ? $"{Tokens.Variant}" - : variable.IsEnumField() && variable.AsTypeDeclaration.HasPrivateAccessibility() - ? $"{Tokens.Long}" - : $"{variable.AsTypeName}"; - - var asTypeClause = $"{Tokens.As} {asType}"; - - var paramAccessibility = variable.IsUserDefinedType() ? Tokens.ByRef : Tokens.ByVal; - - var letSetParameter = $"{paramAccessibility} {propertyValueParam} {Tokens.As} {asType}"; - - switch (letSetGetType) - { - case DeclarationType.PropertyGet: - return string.Join(Environment.NewLine, $"{accessibility ?? Tokens.Public} {PropertyTypeStatement(letSetGetType)} {propertyIdentifier}() {asTypeClause}", content, propertyEndStmt); - case DeclarationType.PropertyLet: - case DeclarationType.PropertySet: - return string.Join(Environment.NewLine, $"{accessibility ?? Tokens.Public} {PropertyTypeStatement(letSetGetType)} {propertyIdentifier}({letSetParameter})", content, propertyEndStmt); - default: - throw new ArgumentException(); - } - } - - private static string PropertyTypeStatement(DeclarationType declarationType) - { - switch (declarationType) - { - case DeclarationType.PropertyGet: - return $"{Tokens.Property} {Tokens.Get}"; - case DeclarationType.PropertyLet: - return $"{Tokens.Property} {Tokens.Let}"; - case DeclarationType.PropertySet: - return $"{Tokens.Property} {Tokens.Set}"; - default: - throw new ArgumentException(); - } - - } } } diff --git a/Rubberduck.Refactorings/Common/ModuleBodyElementDeclarationExtensions.cs b/Rubberduck.Refactorings/Common/ModuleBodyElementDeclarationExtensions.cs deleted file mode 100644 index 5b2bd87671..0000000000 --- a/Rubberduck.Refactorings/Common/ModuleBodyElementDeclarationExtensions.cs +++ /dev/null @@ -1,176 +0,0 @@ -using Rubberduck.Parsing.Grammar; -using Rubberduck.Parsing.Symbols; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Rubberduck.Refactorings.Common -{ - public static class ModuleBodyElementDeclarationExtensions - { - /// - /// Returns ModuleBodyElementDeclaration signature with an ImprovedArgument list - /// 1. Explicitly declares Property Let\Set value parameter as ByVal - /// 2. Ensures UserDefined Type parameters are declared either explicitly or implicitly as ByRef - /// - /// - /// - public static string FullMemberSignature(this ModuleBodyElementDeclaration declaration, - string accessibility = null, - string newIdentifier = null) - { - var accessibilityToken = declaration.Accessibility.Equals(Accessibility.Implicit) - ? Tokens.Public - : $"{declaration.Accessibility.ToString()}"; - - var asTypeClause = declaration.AsTypeName == null - ? string.Empty - : $" {Tokens.As} {declaration.AsTypeName}"; - - var fullSignatureFormat = RetrieveFullSignatureFormat(declaration); - - var fullSignature = string.Format(fullSignatureFormat, - accessibility ?? accessibilityToken, - newIdentifier ?? declaration.IdentifierName, - ImprovedArgumentList(declaration), - asTypeClause); - - return fullSignature.Trim(); - } - - public static string AsCodeBlock(this ModuleBodyElementDeclaration declaration, - string content = null, - string accessibility = null, - string newIdentifier = null) - { - var endStatement = string.Empty; - switch (declaration.Context) - { - case VBAParser.SubStmtContext _: - endStatement = $"{Tokens.End} {Tokens.Sub}"; - break; - case VBAParser.FunctionStmtContext _: - endStatement = $"{Tokens.End} {Tokens.Function}"; - break; - case VBAParser.PropertyGetStmtContext _: - case VBAParser.PropertyLetStmtContext _: - case VBAParser.PropertySetStmtContext _: - endStatement = $"{Tokens.End} {Tokens.Property}"; - break; - default: - throw new ArgumentException(); - } - - if (content != null) - { - return string.Format("{0}{1}{2}{1}{3}{1}", - FullMemberSignature(declaration, accessibility, newIdentifier), - Environment.NewLine, - content, - endStatement); - } - - return string.Format("{0}{1}{2}{1}", - FullMemberSignature(declaration, accessibility, newIdentifier), - Environment.NewLine, - endStatement); - } - - /// - /// 1. Explicitly declares Property Let\Set value parameter as ByVal - /// 2. Ensures UserDefined Type parameters are declared either explicitly or implicitly as ByRef - /// - /// - /// - public static string ImprovedArgumentList(this ModuleBodyElementDeclaration declaration) - { - var arguments = Enumerable.Empty(); - if (declaration is IParameterizedDeclaration parameterizedDeclaration) - { - arguments = parameterizedDeclaration.Parameters - .OrderBy(parameter => parameter.Selection) - .Select(parameter => BuildParameterDeclaration( - parameter, - parameter.Equals(parameterizedDeclaration.Parameters.LastOrDefault()) - && declaration.DeclarationType.HasFlag(DeclarationType.Property) - && !declaration.DeclarationType.Equals(DeclarationType.PropertyGet))); - } - return $"{string.Join(", ", arguments)}"; - } - - private static string BuildParameterDeclaration(ParameterDeclaration parameter, bool forceExplicitByValAccess) - { - var accessibility = parameter.IsImplicitByRef - ? string.Empty - : parameter.IsByRef - ? Tokens.ByRef - : Tokens.ByVal; - - if (forceExplicitByValAccess) - { - accessibility = Tokens.ByVal; - } - - if (accessibility.Equals(Tokens.ByVal) - && (parameter.AsTypeDeclaration?.DeclarationType.HasFlag(DeclarationType.UserDefinedType) ?? false)) - { - accessibility = Tokens.ByRef; - } - - var name = parameter.IsArray - ? $"{parameter.IdentifierName}()" - : parameter.IdentifierName; - - var optional = parameter.IsParamArray - ? Tokens.ParamArray - : parameter.IsOptional - ? Tokens.Optional - : string.Empty; - - var defaultValue = parameter.DefaultValue; - - return $"{FormatStandardElement(optional)}{FormatStandardElement(accessibility)}{FormatStandardElement(name)}{FormattedAsTypeName(parameter.AsTypeName)}{FormattedDefaultValue(defaultValue)}".Trim(); - } - - private static string RetrieveFullSignatureFormat(Declaration declaration) - { - var fullSignatureFormat = $"{{0}} THE_MEMBER_TYPE {{1}}({{2}}){{3}}"; - - switch (declaration.Context) - { - case VBAParser.SubStmtContext _: - fullSignatureFormat = fullSignatureFormat.Replace("THE_MEMBER_TYPE", Tokens.Sub); - break; - case VBAParser.FunctionStmtContext _: - fullSignatureFormat = fullSignatureFormat.Replace("THE_MEMBER_TYPE", Tokens.Function); - break; - case VBAParser.PropertyGetStmtContext _: - fullSignatureFormat = fullSignatureFormat.Replace("THE_MEMBER_TYPE", $"{Tokens.Property} {Tokens.Get}"); - break; - case VBAParser.PropertyLetStmtContext _: - fullSignatureFormat = fullSignatureFormat.Replace("THE_MEMBER_TYPE", $"{Tokens.Property} {Tokens.Let}"); - break; - case VBAParser.PropertySetStmtContext _: - fullSignatureFormat = fullSignatureFormat.Replace("THE_MEMBER_TYPE", $"{Tokens.Property} {Tokens.Set}"); - break; - default: - throw new ArgumentException(); - } - return fullSignatureFormat; - } - - private static string FormatStandardElement(string element) => string.IsNullOrEmpty(element) - ? string.Empty - : $"{element} "; - - private static string FormattedAsTypeName(string AsTypeName) => string.IsNullOrEmpty(AsTypeName) - ? string.Empty - : $"As {AsTypeName} "; - - private static string FormattedDefaultValue(string DefaultValue) => string.IsNullOrEmpty(DefaultValue) - ? string.Empty - : $"= {DefaultValue}"; - } -} \ No newline at end of file diff --git a/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldRefactoring.cs b/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldRefactoring.cs index 3b92a5463d..78003eddce 100644 --- a/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldRefactoring.cs +++ b/Rubberduck.Refactorings/EncapsulateField/EncapsulateFieldRefactoring.cs @@ -21,6 +21,7 @@ public class EncapsulateFieldRefactoring : InteractiveRefactoringBase userInteraction, IRewritingManager rewritingManager, ISelectionProvider selectionProvider, - ISelectedDeclarationProvider selectedDeclarationProvider) + ISelectedDeclarationProvider selectedDeclarationProvider, + ICodeBuilder codeBuilder) :base(selectionProvider, userInteraction) { _declarationFinderProvider = declarationFinderProvider; _selectedDeclarationProvider = selectedDeclarationProvider; _indenter = indenter; + _codeBuilder = codeBuilder; _rewritingManager = rewritingManager; } @@ -112,8 +115,8 @@ private IEncapsulateFieldRewriteSession RefactorRewrite(EncapsulateFieldModel mo if (!model.SelectedFieldCandidates.Any()) { return refactorRewriteSession; } var strategy = model.EncapsulateFieldStrategy == EncapsulateFieldStrategy.ConvertFieldsToUDTMembers - ? new ConvertFieldsToUDTMembers(_declarationFinderProvider, model, _indenter) as IEncapsulateStrategy - : new UseBackingFields(_declarationFinderProvider, model, _indenter) as IEncapsulateStrategy; + ? new ConvertFieldsToUDTMembers(_declarationFinderProvider, model, _indenter, _codeBuilder) as IEncapsulateStrategy + : new UseBackingFields(_declarationFinderProvider, model, _indenter, _codeBuilder) as IEncapsulateStrategy; return strategy.RefactorRewrite(refactorRewriteSession, asPreview); } diff --git a/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/ConvertFieldsToUDTMembers.cs b/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/ConvertFieldsToUDTMembers.cs index a0c7d3da00..57289c1db1 100644 --- a/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/ConvertFieldsToUDTMembers.cs +++ b/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/ConvertFieldsToUDTMembers.cs @@ -21,8 +21,8 @@ public class ConvertFieldsToUDTMembers : EncapsulateFieldStrategyBase { private IObjectStateUDT _stateUDTField; - public ConvertFieldsToUDTMembers(IDeclarationFinderProvider declarationFinderProvider, EncapsulateFieldModel model, IIndenter indenter) - : base(declarationFinderProvider, model, indenter) + public ConvertFieldsToUDTMembers(IDeclarationFinderProvider declarationFinderProvider, EncapsulateFieldModel model, IIndenter indenter, ICodeBuilder codeBuilder) + : base(declarationFinderProvider, model, indenter, codeBuilder) { _stateUDTField = model.ObjectStateUDTField; } @@ -66,40 +66,6 @@ protected override void LoadNewDeclarationBlocks() return; } - protected override void LoadNewPropertyBlocks() - { - var propertyGenerationSpecs = SelectedFields.SelectMany(f => f.PropertyAttributeSets); - - foreach (var selectedField in SelectedFields) - { - var converted = selectedField as IConvertToUDTMember; - foreach (var set in selectedField.PropertyAttributeSets) - { - if (converted.Declaration is VariableDeclaration variableDeclaration) - { - var getContent = $"{set.PropertyName} = {set.BackingField}"; - if (set.UsesSetAssignment) - { - getContent = $"{Tokens.Set} {getContent}"; - } - AddContentBlock(NewContentTypes.MethodBlock, variableDeclaration.FieldToPropertyBlock(DeclarationType.PropertyGet, set.PropertyName, content: $"{_defaultIndent}{getContent}")); - if (converted.IsReadOnly) - { - continue; - } - if (set.GenerateLetter) - { - AddContentBlock(NewContentTypes.MethodBlock, variableDeclaration.FieldToPropertyBlock(DeclarationType.PropertyLet, set.PropertyName, content: $"{_defaultIndent}{set.BackingField} = {set.ParameterName}")); - } - if (set.GenerateSetter) - { - AddContentBlock(NewContentTypes.MethodBlock, variableDeclaration.FieldToPropertyBlock(DeclarationType.PropertySet, set.PropertyName, content: $"{_defaultIndent}{Tokens.Set} {set.BackingField} = {set.ParameterName}")); - } - } - } - } - } - protected override void LoadFieldReferenceContextReplacements(IEncapsulateFieldCandidate field) { Debug.Assert(field is IConvertToUDTMember); diff --git a/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/EncapsulateFieldStrategyBase.cs b/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/EncapsulateFieldStrategyBase.cs index c84a667889..bf6c9f2d12 100644 --- a/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/EncapsulateFieldStrategyBase.cs +++ b/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/EncapsulateFieldStrategyBase.cs @@ -10,6 +10,7 @@ using Rubberduck.VBEditor; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -41,6 +42,7 @@ public abstract class EncapsulateFieldStrategyBase : IEncapsulateStrategy protected QualifiedModuleName _targetQMN; private readonly int? _codeSectionStartIndex; protected const string _defaultIndent = " "; //4 spaces + protected ICodeBuilder _codeBuilder; protected Dictionary IdentifierReplacements { get; } = new Dictionary(); @@ -50,10 +52,11 @@ protected enum NewContentTypes { TypeDeclarationBlock, DeclarationBlock, MethodB protected IEnumerable SelectedFields { private set; get; } - public EncapsulateFieldStrategyBase(IDeclarationFinderProvider declarationFinderProvider, EncapsulateFieldModel model, IIndenter indenter) + public EncapsulateFieldStrategyBase(IDeclarationFinderProvider declarationFinderProvider, EncapsulateFieldModel model, IIndenter indenter, ICodeBuilder codeBuilder) { _targetQMN = model.QualifiedModuleName; _indenter = indenter; + _codeBuilder = codeBuilder; SelectedFields = model.SelectedFieldCandidates; _codeSectionStartIndex = declarationFinderProvider.DeclarationFinder @@ -138,39 +141,62 @@ private void InsertNewContent(IEncapsulateFieldRewriteSession refactorRewriteSes } } - protected virtual void LoadNewPropertyBlocks() + protected void LoadNewPropertyBlocks() { - foreach (var attributeSet in SelectedFields.SelectMany(f => f.PropertyAttributeSets)) + foreach (var propertyAttributes in SelectedFields.SelectMany(f => f.PropertyAttributeSets)) { - if (attributeSet.Declaration is VariableDeclaration || attributeSet.Declaration.DeclarationType.Equals(DeclarationType.UserDefinedTypeMember)) - { - var getContent = $"{attributeSet.PropertyName} = {attributeSet.BackingField}"; - if (attributeSet.UsesSetAssignment) - { - getContent = $"{Tokens.Set} {getContent}"; - } - if (attributeSet.AsTypeName.Equals(Tokens.Variant) && !attributeSet.Declaration.IsArray) - { - getContent = string.Join(Environment.NewLine, - $"{Tokens.If} IsObject({attributeSet.BackingField}) {Tokens.Then}", - $"{_defaultIndent}{Tokens.Set} {attributeSet.PropertyName} = {attributeSet.BackingField}", - Tokens.Else, - $"{_defaultIndent}{attributeSet.PropertyName} = {attributeSet.BackingField}", - $"{Tokens.End} {Tokens.If}", - Environment.NewLine); - } + AddPropertyCodeBlocks(propertyAttributes); + } + } + + private void AddPropertyCodeBlocks(PropertyAttributeSet propertyAttributes) + { + Debug.Assert(propertyAttributes.Declaration.DeclarationType.HasFlag(DeclarationType.Variable) || propertyAttributes.Declaration.DeclarationType.HasFlag(DeclarationType.UserDefinedTypeMember)); - AddContentBlock(NewContentTypes.MethodBlock, attributeSet.Declaration.FieldToPropertyBlock(DeclarationType.PropertyGet, attributeSet.PropertyName, content: $"{_defaultIndent}{getContent}")); + var getContent = $"{propertyAttributes.PropertyName} = {propertyAttributes.BackingField}"; + if (propertyAttributes.UsesSetAssignment) + { + getContent = $"{Tokens.Set} {getContent}"; + } - if (attributeSet.GenerateLetter) - { - AddContentBlock(NewContentTypes.MethodBlock, attributeSet.Declaration.FieldToPropertyBlock(DeclarationType.PropertyLet, attributeSet.PropertyName, content: $"{_defaultIndent}{attributeSet.BackingField} = {attributeSet.ParameterName}")); - } - if (attributeSet.GenerateSetter) - { - AddContentBlock(NewContentTypes.MethodBlock, attributeSet.Declaration.FieldToPropertyBlock(DeclarationType.PropertySet, attributeSet.PropertyName, content: $"{_defaultIndent}{Tokens.Set} {attributeSet.BackingField} = {attributeSet.ParameterName}")); - } + if (propertyAttributes.AsTypeName.Equals(Tokens.Variant) && !propertyAttributes.Declaration.IsArray) + { + getContent = string.Join(Environment.NewLine, + $"{Tokens.If} IsObject({propertyAttributes.BackingField}) {Tokens.Then}", + $"{_defaultIndent}{Tokens.Set} {propertyAttributes.PropertyName} = {propertyAttributes.BackingField}", + Tokens.Else, + $"{_defaultIndent}{propertyAttributes.PropertyName} = {propertyAttributes.BackingField}", + $"{Tokens.End} {Tokens.If}", + Environment.NewLine); + } + + if (!_codeBuilder.TryBuildPropertyGetCodeBlock(propertyAttributes.Declaration, propertyAttributes.PropertyName, out var propertyGet, content: $"{_defaultIndent}{getContent}")) + { + throw new ArgumentException(); + } + AddContentBlock(NewContentTypes.MethodBlock, propertyGet); + + if (!(propertyAttributes.GenerateLetter || propertyAttributes.GenerateSetter)) + { + return; + } + + if (propertyAttributes.GenerateLetter) + { + if (!_codeBuilder.TryBuildPropertyLetCodeBlock(propertyAttributes.Declaration, propertyAttributes.PropertyName, out var propertyLet, content: $"{_defaultIndent}{propertyAttributes.BackingField} = {propertyAttributes.ParameterName}")) + { + throw new ArgumentException(); + } + AddContentBlock(NewContentTypes.MethodBlock, propertyLet); + } + + if (propertyAttributes.GenerateSetter) + { + if (!_codeBuilder.TryBuildPropertySetCodeBlock(propertyAttributes.Declaration, propertyAttributes.PropertyName, out var propertySet, content: $"{_defaultIndent}{Tokens.Set} {propertyAttributes.BackingField} = {propertyAttributes.ParameterName}")) + { + throw new ArgumentException(); } + AddContentBlock(NewContentTypes.MethodBlock, propertySet); } } diff --git a/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/UseBackingFields.cs b/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/UseBackingFields.cs index 7172200a33..b4f5b16dfb 100644 --- a/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/UseBackingFields.cs +++ b/Rubberduck.Refactorings/EncapsulateField/EncapsulationStrategies/UseBackingFields.cs @@ -16,8 +16,8 @@ namespace Rubberduck.Refactorings.EncapsulateField { public class UseBackingFields : EncapsulateFieldStrategyBase { - public UseBackingFields(IDeclarationFinderProvider declarationFinderProvider, EncapsulateFieldModel model, IIndenter indenter) - : base(declarationFinderProvider, model, indenter){ } + public UseBackingFields(IDeclarationFinderProvider declarationFinderProvider, EncapsulateFieldModel model, IIndenter indenter, ICodeBuilder codeBuilder) + : base(declarationFinderProvider, model, indenter, codeBuilder) { } protected override void ModifyFields(IEncapsulateFieldRewriteSession refactorRewriteSession) { diff --git a/Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceModel.cs b/Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceModel.cs index 22af322d07..ec40082048 100644 --- a/Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceModel.cs +++ b/Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceModel.cs @@ -34,7 +34,7 @@ public class ExtractInterfaceModel : IRefactoringModel DeclarationType.PropertySet, }; - public ExtractInterfaceModel(IDeclarationFinderProvider declarationFinderProvider, ClassModuleDeclaration target) + public ExtractInterfaceModel(IDeclarationFinderProvider declarationFinderProvider, ClassModuleDeclaration target, ICodeBuilder codeBuilder) { TargetDeclaration = target; DeclarationFinderProvider = declarationFinderProvider; @@ -47,10 +47,10 @@ public ExtractInterfaceModel(IDeclarationFinderProvider declarationFinderProvide InterfaceName = $"I{TargetDeclaration.IdentifierName}"; InterfaceInstancing = ImplementingClassInstancing; - LoadMembers(); + LoadMembers(codeBuilder); } - private void LoadMembers() + private void LoadMembers(ICodeBuilder codeBuilder) { Members = new ObservableCollection(DeclarationFinderProvider.DeclarationFinder .Members(TargetDeclaration.QualifiedModuleName) @@ -59,7 +59,7 @@ private void LoadMembers() && MemberTypes.Contains(item.DeclarationType)) .OrderBy(o => o.Selection.StartLine) .ThenBy(t => t.Selection.StartColumn) - .Select(d => new InterfaceMember(d)) + .Select(d => new InterfaceMember(d, codeBuilder)) .ToList()); } } diff --git a/Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceRefactoring.cs b/Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceRefactoring.cs index 692abd59e8..090c11a860 100644 --- a/Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceRefactoring.cs +++ b/Rubberduck.Refactorings/ExtractInterface/ExtractInterfaceRefactoring.cs @@ -13,16 +13,19 @@ public class ExtractInterfaceRefactoring : InteractiveRefactoringBase _refactoringAction; private readonly IDeclarationFinderProvider _declarationFinderProvider; + private readonly ICodeBuilder _codeBuilder; public ExtractInterfaceRefactoring( ExtractInterfaceRefactoringAction refactoringAction, IDeclarationFinderProvider declarationFinderProvider, RefactoringUserInteraction userInteraction, - ISelectionProvider selectionProvider) + ISelectionProvider selectionProvider, + ICodeBuilder codeBuilder) :base(selectionProvider, userInteraction) { _refactoringAction = refactoringAction; _declarationFinderProvider = declarationFinderProvider; + _codeBuilder = codeBuilder; } private static readonly DeclarationType[] ModuleTypes = @@ -55,7 +58,7 @@ protected override ExtractInterfaceModel InitializeModel(Declaration target) throw new InvalidDeclarationTypeException(target); } - return new ExtractInterfaceModel(_declarationFinderProvider, targetClass); + return new ExtractInterfaceModel(_declarationFinderProvider, targetClass, _codeBuilder); } protected override void RefactorImpl(ExtractInterfaceModel model) diff --git a/Rubberduck.Refactorings/ExtractInterface/InterfaceMember.cs b/Rubberduck.Refactorings/ExtractInterface/InterfaceMember.cs index 7cfaf3ed05..e8b77e74f8 100644 --- a/Rubberduck.Refactorings/ExtractInterface/InterfaceMember.cs +++ b/Rubberduck.Refactorings/ExtractInterface/InterfaceMember.cs @@ -1,20 +1,16 @@ using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Runtime.CompilerServices; -using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Symbols; -using Rubberduck.Refactorings.Common; -using Rubberduck.Refactorings.ImplementInterface; namespace Rubberduck.Refactorings.ExtractInterface { public class InterfaceMember : INotifyPropertyChanged { private readonly ModuleBodyElementDeclaration _element; + private readonly ICodeBuilder _codeBuilder; - public InterfaceMember(Declaration member) + public InterfaceMember(Declaration member, ICodeBuilder codeBuilder) { Member = member; if (!(member is ModuleBodyElementDeclaration mbed)) @@ -22,6 +18,7 @@ public InterfaceMember(Declaration member) throw new ArgumentException(); } _element = mbed; + _codeBuilder = codeBuilder; } public Declaration Member { get; } @@ -37,9 +34,9 @@ public bool IsSelected } } - public string FullMemberSignature => _element.FullMemberSignature(); + public string FullMemberSignature => _codeBuilder.ImprovedFullMemberSignature(_element); - public string Body => _element.AsCodeBlock(); + public string Body => _codeBuilder.BuildMemberBlockFromPrototype(_element); public event PropertyChangedEventHandler PropertyChanged; diff --git a/Rubberduck.Refactorings/ImplementInterface/AddInterfaceImplementations/AddInterfaceImplementationsRefactoringAction.cs b/Rubberduck.Refactorings/ImplementInterface/AddInterfaceImplementations/AddInterfaceImplementationsRefactoringAction.cs index 9cc0003d18..0c3091b84a 100644 --- a/Rubberduck.Refactorings/ImplementInterface/AddInterfaceImplementations/AddInterfaceImplementationsRefactoringAction.cs +++ b/Rubberduck.Refactorings/ImplementInterface/AddInterfaceImplementations/AddInterfaceImplementationsRefactoringAction.cs @@ -4,17 +4,19 @@ using Rubberduck.Parsing.Grammar; using Rubberduck.Parsing.Rewriter; using Rubberduck.Parsing.Symbols; -using Rubberduck.Refactorings.Common; + namespace Rubberduck.Refactorings.AddInterfaceImplementations { public class AddInterfaceImplementationsRefactoringAction : CodeOnlyRefactoringActionBase { private readonly string _memberBody; + private readonly ICodeBuilder _codeBuilder; - public AddInterfaceImplementationsRefactoringAction(IRewritingManager rewritingManager) + public AddInterfaceImplementationsRefactoringAction(IRewritingManager rewritingManager, ICodeBuilder codeBuilder) : base(rewritingManager) { + _codeBuilder = codeBuilder; _memberBody = $" {Tokens.Err}.Raise 5 {Resources.Refactorings.Refactorings.ImplementInterface_TODO}"; } @@ -36,23 +38,33 @@ private string GetInterfaceMember(Declaration member, string interfaceName) { if (member is ModuleBodyElementDeclaration mbed) { - return mbed.AsCodeBlock(accessibility: Tokens.Private, newIdentifier: $"{interfaceName}_{member.IdentifierName}", content: _memberBody); + return _codeBuilder.BuildMemberBlockFromPrototype(mbed, accessibility: Tokens.Private, newIdentifier: $"{interfaceName}_{member.IdentifierName}", content: _memberBody); } - if (member.DeclarationType.Equals(DeclarationType.Variable)) + if (member is VariableDeclaration variable) { - var propertyGet = member.FieldToPropertyBlock(DeclarationType.PropertyGet, $"{interfaceName}_{member.IdentifierName}", Tokens.Private, _memberBody); + if (!_codeBuilder.TryBuildPropertyGetCodeBlock(variable, $"{interfaceName}_{variable.IdentifierName}", out var propertyGet, Tokens.Private, _memberBody)) + { + throw new InvalidOperationException(); + } + var members = new List { propertyGet }; - if (member.AsTypeName.Equals(Tokens.Variant) || !member.IsObject) + if (variable.AsTypeName.Equals(Tokens.Variant) || !variable.IsObject) { - var propertyLet = member.FieldToPropertyBlock(DeclarationType.PropertyLet, $"{interfaceName}_{member.IdentifierName}", Tokens.Private, _memberBody); + if (!_codeBuilder.TryBuildPropertyLetCodeBlock(variable, $"{interfaceName}_{variable.IdentifierName}", out var propertyLet, Tokens.Private, _memberBody)) + { + throw new InvalidOperationException(); + } members.Add(propertyLet); } - if (member.AsTypeName.Equals(Tokens.Variant) || member.IsObject) + if (variable.AsTypeName.Equals(Tokens.Variant) || variable.IsObject) { - var propertySet = member.FieldToPropertyBlock(DeclarationType.PropertySet, $"{interfaceName}_{member.IdentifierName}", Tokens.Private, _memberBody); + if (!_codeBuilder.TryBuildPropertySetCodeBlock(variable, $"{interfaceName}_{variable.IdentifierName}", out var propertySet, Tokens.Private, _memberBody)) + { + throw new InvalidOperationException(); + } members.Add(propertySet); } diff --git a/RubberduckTests/CodeBuilderTests.cs b/RubberduckTests/CodeBuilderTests.cs new file mode 100644 index 0000000000..58326a60be --- /dev/null +++ b/RubberduckTests/CodeBuilderTests.cs @@ -0,0 +1,413 @@ +using NUnit.Framework; +using Rubberduck.Parsing.Symbols; +using Rubberduck.Refactorings; +using RubberduckTests.Mocks; +using System; +using System.Linq; + +namespace RubberduckTests +{ + [TestFixture] + public class CodeBuilderTests + { + + [TestCase("fizz", DeclarationType.Variable, "Integer")] + [TestCase("FirstValue", DeclarationType.UserDefinedTypeMember, "Long")] + [TestCase("fazz", DeclarationType.Variable, "Long")] + [TestCase("fuzz", DeclarationType.Variable, "ETestType2")] + [Category(nameof(CodeBuilder))] + public void PropertyBlockFromPrototype_PropertyGet(string targetIdentifier, DeclarationType declarationType, string typeName) + { + var testParams = new PropertyBlockFromPrototypeParams("Bazz", DeclarationType.PropertyGet); + string inputCode = +$@" + +Private Type TTestType + FirstValue As Long + SecondValue As Variant +End Type + +Private Enum ETestType + EFirstValue = 0 + ESecondValue +End Enum + +Public Enum ETestType2 + EThirdValue = 0 + EFourthValue +End Enum + +Private fizz As Integer + +Private fazz As ETestType + +Private fuzz As ETestType2 +"; + var result = ParseAndTest(inputCode, + targetIdentifier, + declarationType, + testParams, + PropertyGetBlockFromPrototypeTest); + + StringAssert.Contains($"Property Get {testParams.Identifier}() As {typeName}", result); + } + + [TestCase("fizz", DeclarationType.Variable, "Integer", "Public")] + [TestCase("FirstValue", DeclarationType.UserDefinedTypeMember, "Long", "Public")] + [TestCase("fazz", DeclarationType.Variable, "Long", "Public")] + [TestCase("fuzz", DeclarationType.Variable, "ETestType2", "Private")] + [Category(nameof(CodeBuilder))] + public void PropertyBlockFromPrototype_PropertyGetAccessibility(string targetIdentifier, DeclarationType declarationType, string typeName, string accessibility) + { + var testParams = new PropertyBlockFromPrototypeParams("Bazz", DeclarationType.PropertyGet, accessibility); + string inputCode = +$@" + +Private Type TTestType + FirstValue As Long + SecondValue As Variant +End Type + +Private Enum ETestType + EFirstValue = 0 + ESecondValue +End Enum + +Public Enum ETestType2 + EThirdValue = 0 + EFourthValue +End Enum + +Private fizz As Integer + +Private fazz As ETestType + +Private fuzz As ETestType2 +"; + var result = ParseAndTest(inputCode, + targetIdentifier, + declarationType, + testParams, + PropertyGetBlockFromPrototypeTest); + + StringAssert.Contains($"{accessibility} Property Get {testParams.Identifier}() As {typeName}", result); + } + + [TestCase("fizz", DeclarationType.Variable, "Integer", "Bazz = fizz")] + [TestCase("FirstValue", DeclarationType.UserDefinedTypeMember, "Long", "Bazz = fozz.FirstValue")] + [TestCase("fazz", DeclarationType.Variable, "Long", "Bazz = fazz")] + [TestCase("fuzz", DeclarationType.Variable, "TTestType2", "Bazz = fuzz")] + [Category(nameof(CodeBuilder))] + public void PropertyBlockFromPrototype_PropertyGetContent(string targetIdentifier, DeclarationType declarationType, string typeName, string content) + { + var testParams = new PropertyBlockFromPrototypeParams("Bazz", DeclarationType.PropertyGet, content: content); + string inputCode = +$@" + +Private Type TTestType + FirstValue As Long + SecondValue As Variant +End Type + +Private Enum ETestType + EFirstValue = 0 + ESecondValue +End Enum + +Public Enum ETestType2 + EThirdValue = 0 + EFourthValue +End Enum + +Private fizz As Integer + +Private fozz As TTestType + +Private fazz As ETestType + +Private fuzz As TTestType2 +"; + var result = ParseAndTest(inputCode, + targetIdentifier, + declarationType, + testParams, + PropertyGetBlockFromPrototypeTest); + + StringAssert.Contains(content, result); + } + + + [TestCase("fizz", DeclarationType.Variable, "Integer", "Bazz = fizz")] + [Category(nameof(CodeBuilder))] + public void PropertyBlockFromPrototype_PropertyGetChangeParamName(string targetIdentifier, DeclarationType declarationType, string typeName, string content) + { + var testParams = new PropertyBlockFromPrototypeParams("Bazz", DeclarationType.PropertyGet, paramIdentifier: "testParam"); + string inputCode = +$@" +Private fizz As Integer +"; + var result = ParseAndTest(inputCode, + targetIdentifier, + declarationType, + testParams, + PropertyGetBlockFromPrototypeTest); + + StringAssert.Contains("Property Get Bazz() As Integer", result); + } + + [TestCase("fizz", DeclarationType.Variable, "Integer")] + [TestCase("FirstValue", DeclarationType.UserDefinedTypeMember, "Long")] + [TestCase("fazz", DeclarationType.Variable, "Long")] + [TestCase("fuzz", DeclarationType.Variable, "ETestType2")] + [Category(nameof(CodeBuilder))] + public void PropertyBlockFromPrototype_PropertyLet(string targetIdentifier, DeclarationType declarationType, string typeName) + { + var testParams = new PropertyBlockFromPrototypeParams("Bazz", DeclarationType.PropertyLet); + string inputCode = +$@" + +Private Type TTestType + FirstValue As Long + SecondValue As Variant +End Type + +Private Enum ETestType + EFirstValue = 0 + ESecondValue +End Enum + +Public Enum ETestType2 + EThirdValue = 0 + EFourthValue +End Enum + +Private fizz As Integer + +Private fazz As ETestType + +Private fuzz As ETestType2 +"; + var result = ParseAndTest(inputCode, + targetIdentifier, + declarationType, + testParams, + PropertyLetBlockFromPrototypeTest); + + StringAssert.Contains($"Property Let {testParams.Identifier}(ByVal value As {typeName})", result); + } + + [TestCase("fizz", DeclarationType.Variable, "Variant")] + [TestCase("SecondValue", DeclarationType.UserDefinedTypeMember, "Variant")] + [Category(nameof(CodeBuilder))] + public void PropertyBlockFromPrototype_PropertySet(string targetIdentifier, DeclarationType declarationType, string typeName) + { + var testParams = new PropertyBlockFromPrototypeParams("Bazz", DeclarationType.PropertySet); + string inputCode = +$@" + +Private Type TTestType + FirstValue As Long + SecondValue As Variant +End Type + +Private fizz As Variant + +"; + var result = ParseAndTest(inputCode, + targetIdentifier, + declarationType, + testParams, + PropertySetBlockFromPrototypeTest); + + StringAssert.Contains($"Property Set {testParams.Identifier}(ByVal value As {typeName})", result); + } + + [TestCase(DeclarationType.PropertyLet)] + [TestCase(DeclarationType.PropertySet)] + [TestCase(DeclarationType.Procedure)] + [Category(nameof(CodeBuilder))] + public void MemberBlockFromPrototype_AppliesByVal(DeclarationType declarationType) + { + var procedureIdentifier = "TestProcedure"; + var procType = ProcedureTypeIdentifier(declarationType); + + string inputCode = +$@" +Public {procType.procType} {procedureIdentifier}(arg1 As Long, arg2 As String) +End {procType.endStmt} +"; + var result = ParseAndTest(inputCode, + procedureIdentifier, + declarationType, + new MemberBlockFromPrototypeTestParams(), + MemberBlockFromPrototypeTest); + + var expected = declarationType.HasFlag(DeclarationType.Property) + ? "(arg1 As Long, ByVal arg2 As String)" + : "(arg1 As Long, arg2 As String)"; + + StringAssert.Contains($"{procType.procType} {procedureIdentifier}{expected}", result); + } + + [TestCase(DeclarationType.PropertyLet)] + [TestCase(DeclarationType.PropertySet)] + [TestCase(DeclarationType.Procedure)] + [Category(nameof(CodeBuilder))] + public void ImprovedArgumentList_AppliesByVal(DeclarationType declarationType) + { + var procedureIdentifier = "TestProperty"; + var procType = ProcedureTypeIdentifier(declarationType); + + string inputCode = +$@" +Public {procType.procType} {procedureIdentifier}(arg1 As Long, arg2 As String) +End {procType.endStmt} +"; + var result = ParseAndTest(inputCode, + procedureIdentifier, + declarationType, + ImprovedArgumentListTest); + + var expected = declarationType.HasFlag(DeclarationType.Property) + ? "arg1 As Long, ByVal arg2 As String" + : "arg1 As Long, arg2 As String"; + + StringAssert.AreEqualIgnoringCase(expected, result); + } + + + [TestCase(DeclarationType.PropertyGet)] + [TestCase(DeclarationType.Function)] + [Category(nameof(CodeBuilder))] + public void ImprovedArgumentList_FunctionTypes(DeclarationType declarationType) + { + var procedureIdentifier = "TestProperty"; + var procType = ProcedureTypeIdentifier(declarationType); + + string inputCode = +$@" +Public {procType.procType} {procedureIdentifier}(arg1 As Long, arg2 As String) As Long +End {procType.endStmt} +"; + var result = ParseAndTest(inputCode, + procedureIdentifier, + declarationType, + ImprovedArgumentListTest); + + StringAssert.AreEqualIgnoringCase($"arg1 As Long, arg2 As String", result); + } + + private string ParseAndTest(string inputCode, string targetIdentifier, DeclarationType declarationType, Func theTest) + { + var vbe = MockVbeBuilder.BuildFromSingleStandardModule(inputCode, out _).Object; + var state = MockParser.CreateAndParse(vbe); + using (state) + { + var target = state.DeclarationFinder.DeclarationsWithType(declarationType) + .Where(d => d.IdentifierName == targetIdentifier).OfType() + .Single(); + return theTest(target); + } + } + + private string ParseAndTest(string inputCode, string targetIdentifier, DeclarationType declarationType, MemberBlockFromPrototypeTestParams testParams, Func theTest) + { + var vbe = MockVbeBuilder.BuildFromSingleStandardModule(inputCode, out _).Object; + var state = MockParser.CreateAndParse(vbe); + using (state) + { + var target = state.DeclarationFinder.DeclarationsWithType(declarationType) + .Where(d => d.IdentifierName == targetIdentifier).OfType() + .Single(); + return theTest(target, testParams); + } + } + + private string ParseAndTest(string inputCode, string targetIdentifier, DeclarationType declarationType, PropertyBlockFromPrototypeParams testParams, Func theTest) + { + var vbe = MockVbeBuilder.BuildFromSingleStandardModule(inputCode, out _).Object; + var state = MockParser.CreateAndParse(vbe); + using (state) + { + var target = state.DeclarationFinder.DeclarationsWithType(declarationType) + .Where(d => d.IdentifierName == targetIdentifier).OfType() + .Single(); + return theTest(target, testParams); + } + } + + private static string PropertyGetBlockFromPrototypeTest(T target, PropertyBlockFromPrototypeParams testParams) where T : Declaration + { + new CodeBuilder().TryBuildPropertyGetCodeBlock(target, testParams.Identifier, out string result, testParams.Accessibility, testParams.Content); //, testParams.WriteParam); + return result; + } + + private static string PropertyLetBlockFromPrototypeTest(T target, PropertyBlockFromPrototypeParams testParams) where T : Declaration + { + new CodeBuilder().TryBuildPropertyLetCodeBlock(target, testParams.Identifier, out string result, testParams.Accessibility, testParams.Content, testParams.WriteParam); + return result; + } + + private static string PropertySetBlockFromPrototypeTest(T target, PropertyBlockFromPrototypeParams testParams) where T : Declaration + { + new CodeBuilder().TryBuildPropertySetCodeBlock(target, testParams.Identifier, out string result, testParams.Accessibility, testParams.Content, testParams.WriteParam); + return result; + } + + private static string ImprovedArgumentListTest(ModuleBodyElementDeclaration mbed) + => new CodeBuilder().ImprovedArgumentList(mbed); + + private static string MemberBlockFromPrototypeTest(ModuleBodyElementDeclaration mbed, MemberBlockFromPrototypeTestParams testParams) + => new CodeBuilder().BuildMemberBlockFromPrototype(mbed, testParams.Accessibility, testParams.Content, testParams.NewIdentifier); + + private (string procType, string endStmt) ProcedureTypeIdentifier(DeclarationType declarationType) + { + switch (declarationType) + { + case DeclarationType.Function: + return ("Function", "Function"); + case DeclarationType.Procedure: + return ("Sub", "Sub"); + case DeclarationType.PropertyGet: + return ("Property Get", "Property"); + case DeclarationType.PropertyLet: + return ("Property Let", "Property"); + case DeclarationType.PropertySet: + return ("Property Set", "Property"); + default: + throw new ArgumentOutOfRangeException(); + } + } + + private struct PropertyBlockFromPrototypeParams + { + public PropertyBlockFromPrototypeParams(string identifier, DeclarationType propertyType, string accessibility = null, string content = null, string paramIdentifier = null) + { + Identifier = identifier; + DeclarationType = propertyType; + Accessibility = accessibility; + Content = content; + WriteParam = paramIdentifier; + } + public DeclarationType DeclarationType { get; } + public string Identifier { get; } + public string Accessibility {get; } + public string Content { get; } + public string WriteParam { get; } + } + + private struct MemberBlockFromPrototypeTestParams + { + public MemberBlockFromPrototypeTestParams(ModuleBodyElementDeclaration mbed, string accessibility = null, string content = null, string newIdentifier = null) + { + Accessibility = accessibility; + Content = content; + NewIdentifier = newIdentifier; + } + + public string Accessibility { get; } + public string Content { get; } + public string NewIdentifier { get; } + } + } +} diff --git a/RubberduckTests/CodeExplorer/MockedCodeExplorer.cs b/RubberduckTests/CodeExplorer/MockedCodeExplorer.cs index 0eab0cae07..f37cfef86f 100644 --- a/RubberduckTests/CodeExplorer/MockedCodeExplorer.cs +++ b/RubberduckTests/CodeExplorer/MockedCodeExplorer.cs @@ -34,6 +34,7 @@ using Rubberduck.VBEditor.SourceCodeHandling; using Rubberduck.VBEditor.Utility; using RubberduckTests.Settings; +using Rubberduck.Refactorings; namespace RubberduckTests.CodeExplorer { @@ -506,12 +507,12 @@ public MockedCodeExplorer ImplementIndenterCommand() public MockedCodeExplorer ImplementExtractInterfaceCommand() { - var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(null); + var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(null, new CodeBuilder()); var addComponentService = TestAddComponentService(State.ProjectsProvider); var extractInterfaceBaseRefactoring = new ExtractInterfaceRefactoringAction(addImplementationsBaseRefactoring, State, State, null, State.ProjectsProvider, addComponentService); var userInteraction = new RefactoringUserInteraction(null, _uiDispatcher.Object); ViewModel.CodeExplorerExtractInterfaceCommand = new CodeExplorerExtractInterfaceCommand( - new ExtractInterfaceRefactoring(extractInterfaceBaseRefactoring, State, userInteraction, null), + new ExtractInterfaceRefactoring(extractInterfaceBaseRefactoring, State, userInteraction, null, new CodeBuilder()), State, null, VbeEvents.Object); return this; } diff --git a/RubberduckTests/Commands/RefactorCommands/EncapsulateFieldCommandTests.cs b/RubberduckTests/Commands/RefactorCommands/EncapsulateFieldCommandTests.cs index 24f9382554..2d9a377960 100644 --- a/RubberduckTests/Commands/RefactorCommands/EncapsulateFieldCommandTests.cs +++ b/RubberduckTests/Commands/RefactorCommands/EncapsulateFieldCommandTests.cs @@ -64,7 +64,7 @@ protected override CommandBase TestCommand(IVBE vbe, RubberduckParserState state .Setup(m => m.Invoke(It.IsAny())) .Callback((Action action) => action.Invoke()); var userInteraction = new RefactoringUserInteraction(factory, uiDispatcherMock.Object); - var refactoring = new EncapsulateFieldRefactoring(state, null, userInteraction, rewritingManager, selectionService, selectedDeclarationProvider); + var refactoring = new EncapsulateFieldRefactoring(state, null, userInteraction, rewritingManager, selectionService, selectedDeclarationProvider, new CodeBuilder()); var notifier = new EncapsulateFieldFailedNotifier(msgBox); var selectedDeclarationService = new SelectedDeclarationProvider(selectionService, state); return new RefactorEncapsulateFieldCommand(refactoring, notifier, state, selectionService, selectedDeclarationService); diff --git a/RubberduckTests/Commands/RefactorCommands/ExtractInterfaceCommandTests.cs b/RubberduckTests/Commands/RefactorCommands/ExtractInterfaceCommandTests.cs index 40537193b9..d0678ec0bf 100644 --- a/RubberduckTests/Commands/RefactorCommands/ExtractInterfaceCommandTests.cs +++ b/RubberduckTests/Commands/RefactorCommands/ExtractInterfaceCommandTests.cs @@ -173,11 +173,11 @@ protected override CommandBase TestCommand(IVBE vbe, RubberduckParserState state uiDispatcherMock .Setup(m => m.Invoke(It.IsAny())) .Callback((Action action) => action.Invoke()); - var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(rewritingManager); + var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(rewritingManager, new CodeBuilder()); var addComponentService = TestAddComponentService(state.ProjectsProvider); var baseRefactoring = new ExtractInterfaceRefactoringAction(addImplementationsBaseRefactoring, state, state, rewritingManager, state.ProjectsProvider, addComponentService); var userInteraction = new RefactoringUserInteraction(factory, uiDispatcherMock.Object); - var refactoring = new ExtractInterfaceRefactoring(baseRefactoring, state, userInteraction, selectionService); + var refactoring = new ExtractInterfaceRefactoring(baseRefactoring, state, userInteraction, selectionService, new CodeBuilder()); var notifier = new ExtractInterfaceFailedNotifier(msgBox); return new RefactorExtractInterfaceCommand(refactoring, notifier, state, selectionService); } diff --git a/RubberduckTests/Commands/RefactorCommands/ImplementInterfaceCommandTests.cs b/RubberduckTests/Commands/RefactorCommands/ImplementInterfaceCommandTests.cs index a37e3cea0f..aa5d7590f7 100644 --- a/RubberduckTests/Commands/RefactorCommands/ImplementInterfaceCommandTests.cs +++ b/RubberduckTests/Commands/RefactorCommands/ImplementInterfaceCommandTests.cs @@ -3,6 +3,7 @@ using Rubberduck.Interaction; using Rubberduck.Parsing.Rewriter; using Rubberduck.Parsing.VBA; +using Rubberduck.Refactorings; using Rubberduck.Refactorings.AddInterfaceImplementations; using Rubberduck.Refactorings.ImplementInterface; using Rubberduck.UI.Command; @@ -57,7 +58,7 @@ public void ImplementInterface_CanExecute_ImplementsInterfaceSelected() protected override CommandBase TestCommand(IVBE vbe, RubberduckParserState state, IRewritingManager rewritingManager, ISelectionService selectionService) { var msgBox = new Mock().Object; - var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(rewritingManager); + var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(rewritingManager, new CodeBuilder()); var baseRefactoring = new ImplementInterfaceRefactoringAction(addImplementationsBaseRefactoring, rewritingManager); var refactoring = new ImplementInterfaceRefactoring(baseRefactoring, state, selectionService); var notifier = new ImplementInterfaceFailedNotifier(msgBox); diff --git a/RubberduckTests/Refactoring/EncapsulateField/EncapsulateFIeldTestSupport.cs b/RubberduckTests/Refactoring/EncapsulateField/EncapsulateFIeldTestSupport.cs index 82ed2fa073..b7878733e0 100644 --- a/RubberduckTests/Refactoring/EncapsulateField/EncapsulateFIeldTestSupport.cs +++ b/RubberduckTests/Refactoring/EncapsulateField/EncapsulateFIeldTestSupport.cs @@ -119,7 +119,7 @@ public string RefactoredCode(CodeString codeString, Func() .Single(module => module.IdentifierName == "Class"); - var model = new ExtractInterfaceModel(state, targetClass); + var model = new ExtractInterfaceModel(state, targetClass, new CodeBuilder()); return modelAdjustment(model); } protected override IRefactoringAction TestBaseRefactoring(RubberduckParserState state, IRewritingManager rewritingManager) { - var addInterfaceImplementationsAction = new AddInterfaceImplementationsRefactoringAction(rewritingManager); + var addInterfaceImplementationsAction = new AddInterfaceImplementationsRefactoringAction(rewritingManager, new CodeBuilder()); var addComponentService = TestAddComponentService(state?.ProjectsProvider); return new ExtractInterfaceRefactoringAction(addInterfaceImplementationsAction, state, state, rewritingManager, state?.ProjectsProvider, addComponentService); } diff --git a/RubberduckTests/Refactoring/ExtractInterface/ExtractInterfaceTests.cs b/RubberduckTests/Refactoring/ExtractInterface/ExtractInterfaceTests.cs index 6f19d1b5ea..47970b8836 100644 --- a/RubberduckTests/Refactoring/ExtractInterface/ExtractInterfaceTests.cs +++ b/RubberduckTests/Refactoring/ExtractInterface/ExtractInterfaceTests.cs @@ -150,7 +150,7 @@ public void ExtractInterfaceRefactoring_IgnoresField() .First(); //Specify Params to remove - var model = new ExtractInterfaceModel(state, target); + var model = new ExtractInterfaceModel(state, target, new CodeBuilder()); Assert.AreEqual(0, model.Members.Count); } } @@ -178,7 +178,7 @@ public void ExtractInterfaceRefactoring_DefaultsToPublicInterfaceForExposedImple .First(); //Specify Params to remove - var model = new ExtractInterfaceModel(state, target); + var model = new ExtractInterfaceModel(state, target, new CodeBuilder()); Assert.AreEqual(ClassInstancing.Public, model.InterfaceInstancing); } } @@ -204,7 +204,7 @@ public void ExtractInterfaceRefactoring_DefaultsToPrivateInterfaceForNonExposedI .First(); //Specify Params to remove - var model = new ExtractInterfaceModel(state, target); + var model = new ExtractInterfaceModel(state, target, new CodeBuilder()); Assert.AreEqual(ClassInstancing.Private, model.InterfaceInstancing); } } @@ -309,10 +309,10 @@ End Sub RefactoringUserInteraction userInteraction, ISelectionService selectionService) { - var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(rewritingManager); + var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(rewritingManager, new CodeBuilder()); var addComponentService = TestAddComponentService(state?.ProjectsProvider); var baseRefactoring = new ExtractInterfaceRefactoringAction(addImplementationsBaseRefactoring, state, state, rewritingManager, state?.ProjectsProvider, addComponentService); - return new ExtractInterfaceRefactoring(baseRefactoring, state, userInteraction, selectionService); + return new ExtractInterfaceRefactoring(baseRefactoring, state, userInteraction, selectionService, new CodeBuilder()); } private static IAddComponentService TestAddComponentService(IProjectsProvider projectsProvider) diff --git a/RubberduckTests/Refactoring/ImplementInterface/ImplementInterfaceRefactoringActionTests.cs b/RubberduckTests/Refactoring/ImplementInterface/ImplementInterfaceRefactoringActionTests.cs index 9ee9ca8c9f..f807a1cbc0 100644 --- a/RubberduckTests/Refactoring/ImplementInterface/ImplementInterfaceRefactoringActionTests.cs +++ b/RubberduckTests/Refactoring/ImplementInterface/ImplementInterfaceRefactoringActionTests.cs @@ -772,7 +772,7 @@ private static ImplementInterfaceModel TestModel(RubberduckParserState state) protected override IRefactoringAction TestBaseRefactoring(RubberduckParserState state, IRewritingManager rewritingManager) { - var addInterfaceImplementationsAction = new AddInterfaceImplementationsRefactoringAction(rewritingManager); + var addInterfaceImplementationsAction = new AddInterfaceImplementationsRefactoringAction(rewritingManager, new CodeBuilder()); return new ImplementInterfaceRefactoringAction(addInterfaceImplementationsAction, rewritingManager); } } diff --git a/RubberduckTests/Refactoring/ImplementInterface/ImplementInterfaceTests.cs b/RubberduckTests/Refactoring/ImplementInterface/ImplementInterfaceTests.cs index 01c271821c..aefaaa1aac 100644 --- a/RubberduckTests/Refactoring/ImplementInterface/ImplementInterfaceTests.cs +++ b/RubberduckTests/Refactoring/ImplementInterface/ImplementInterfaceTests.cs @@ -217,7 +217,7 @@ End Sub protected override IRefactoring TestRefactoring(IRewritingManager rewritingManager, RubberduckParserState state, ISelectionService selectionService) { - var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(rewritingManager); + var addImplementationsBaseRefactoring = new AddInterfaceImplementationsRefactoringAction(rewritingManager, new CodeBuilder()); var baseRefactoring = new ImplementInterfaceRefactoringAction(addImplementationsBaseRefactoring, rewritingManager); return new ImplementInterfaceRefactoring(baseRefactoring, state, selectionService); }