|  | 
|  | 1 | +using Antlr4.Runtime; | 
|  | 2 | +using Rubberduck.Parsing; | 
|  | 3 | +using Rubberduck.Parsing.Grammar; | 
|  | 4 | +using Rubberduck.Parsing.Rewriter; | 
|  | 5 | +using Rubberduck.Parsing.Symbols; | 
|  | 6 | +using Rubberduck.Parsing.VBA; | 
|  | 7 | +using System.Collections.Generic; | 
|  | 8 | +using System.Linq; | 
|  | 9 | + | 
|  | 10 | +namespace Rubberduck.Refactorings.EncapsulateField | 
|  | 11 | +{ | 
|  | 12 | +    public interface IEncapsulateFieldReferenceReplacer | 
|  | 13 | +    { | 
|  | 14 | +        void ReplaceReferences<T>(IEnumerable<T> selectedCandidates,  | 
|  | 15 | +            IRewriteSession rewriteSession) where T : IEncapsulateFieldCandidate; | 
|  | 16 | +    } | 
|  | 17 | +    /// <summary> | 
|  | 18 | +    /// EncapsulateFieldReferenceReplacer determines the replacement expressions for existing references | 
|  | 19 | +    /// of encapsulated fields.  It supports both direct encapsulation and encapsulation after wrapping the | 
|  | 20 | +    /// target field in a Private UserDefinedType | 
|  | 21 | +    /// </summary> | 
|  | 22 | +    public class EncapsulateFieldReferenceReplacer : IEncapsulateFieldReferenceReplacer | 
|  | 23 | +    { | 
|  | 24 | +        private readonly IDeclarationFinderProvider _declarationFinderProvider; | 
|  | 25 | +        private readonly IPropertyAttributeSetsGenerator _propertyAttributeSetsGenerator; | 
|  | 26 | +        private readonly IUDTMemberReferenceProvider _udtMemberReferenceProvider; | 
|  | 27 | +        private readonly Dictionary<IdentifierReference, (ParserRuleContext, string)> _identifierReplacements = new Dictionary<IdentifierReference, (ParserRuleContext, string)>(); | 
|  | 28 | + | 
|  | 29 | +        public EncapsulateFieldReferenceReplacer(IDeclarationFinderProvider declarationFinderProvider, | 
|  | 30 | +            IPropertyAttributeSetsGenerator propertyAttributeSetsGenerator, | 
|  | 31 | +            IUDTMemberReferenceProvider udtMemberReferenceProvider) | 
|  | 32 | +        { | 
|  | 33 | +            _declarationFinderProvider = declarationFinderProvider; | 
|  | 34 | +            _propertyAttributeSetsGenerator = propertyAttributeSetsGenerator; | 
|  | 35 | +            _udtMemberReferenceProvider = udtMemberReferenceProvider; | 
|  | 36 | +        } | 
|  | 37 | + | 
|  | 38 | +        public void ReplaceReferences<T>(IEnumerable<T> selectedCandidates, IRewriteSession rewriteSession) where T : IEncapsulateFieldCandidate | 
|  | 39 | +        { | 
|  | 40 | +            if (!selectedCandidates.Any()) | 
|  | 41 | +            { | 
|  | 42 | +                return; | 
|  | 43 | +            } | 
|  | 44 | + | 
|  | 45 | +            ResolveReferenceContextReplacements(selectedCandidates); | 
|  | 46 | + | 
|  | 47 | +            foreach (var kvPair in _identifierReplacements) | 
|  | 48 | +            { | 
|  | 49 | +                var rewriter = rewriteSession.CheckOutModuleRewriter(kvPair.Key.QualifiedModuleName); | 
|  | 50 | +                (ParserRuleContext Context, string Text) = kvPair.Value; | 
|  | 51 | +                rewriter.Replace(Context, Text); | 
|  | 52 | +            } | 
|  | 53 | +        } | 
|  | 54 | + | 
|  | 55 | +        private void ResolveReferenceContextReplacements<T>(IEnumerable<T> selectedCandidates ) where T : IEncapsulateFieldCandidate | 
|  | 56 | +        { | 
|  | 57 | +            var selectedVariableDeclarations = selectedCandidates.Select(sc => sc.Declaration).Cast<VariableDeclaration>(); | 
|  | 58 | + | 
|  | 59 | +            var udtFieldToMemberReferences = _udtMemberReferenceProvider.UdtFieldToMemberReferences(_declarationFinderProvider, selectedVariableDeclarations); | 
|  | 60 | + | 
|  | 61 | +            foreach (var field in selectedCandidates) | 
|  | 62 | +            { | 
|  | 63 | +                if (IsInstanceOfPrivateUDT(field)) | 
|  | 64 | +                { | 
|  | 65 | +                    if (udtFieldToMemberReferences.TryGetValue(field.Declaration, out var relevantReferences)) | 
|  | 66 | +                    { | 
|  | 67 | +                        ResolvePrivateUDTMemberReferenceReplacements(field, relevantReferences); | 
|  | 68 | +                    } | 
|  | 69 | +                    continue; | 
|  | 70 | +                } | 
|  | 71 | +                 | 
|  | 72 | +                ResolveNonPrivateUDTFieldReferenceReplacements(field); | 
|  | 73 | +            } | 
|  | 74 | +        } | 
|  | 75 | + | 
|  | 76 | +        private void ResolvePrivateUDTMemberReferenceReplacements<T>(T field, IEnumerable<IdentifierReference> udtMemberReferencesToChange) where T: IEncapsulateFieldCandidate | 
|  | 77 | +        { | 
|  | 78 | +            if (field is IEncapsulateFieldAsUDTMemberCandidate wrappedField) | 
|  | 79 | +            { | 
|  | 80 | +                var wrappedField_WithStmtContexts = wrappedField.Declaration.References | 
|  | 81 | +                    .Where(rf => rf.Context.Parent.Parent is VBAParser.WithStmtContext) | 
|  | 82 | +                    .Select(rf => (rf, rf.Context)); | 
|  | 83 | + | 
|  | 84 | +                foreach ((IdentifierReference idRef, ParserRuleContext prCtxt) in wrappedField_WithStmtContexts) | 
|  | 85 | +                { | 
|  | 86 | +                    AddIdentifierReplacement(idRef, prCtxt, $"{wrappedField.ObjectStateUDT.FieldIdentifier}.{wrappedField.PropertyIdentifier}"); | 
|  | 87 | +                } | 
|  | 88 | +            } | 
|  | 89 | + | 
|  | 90 | +            foreach (var paSet in _propertyAttributeSetsGenerator.GeneratePropertyAttributeSets(field)) | 
|  | 91 | +            { | 
|  | 92 | +                foreach (var rf in paSet.Declaration.References.Where(idRef => udtMemberReferencesToChange.Contains(idRef))) | 
|  | 93 | +                { | 
|  | 94 | +                    (ParserRuleContext context, string expression) = GenerateUDTMemberReplacementTuple(field, rf, paSet); | 
|  | 95 | +                    AddIdentifierReplacement(rf, context, expression); | 
|  | 96 | +                } | 
|  | 97 | +            } | 
|  | 98 | +        } | 
|  | 99 | + | 
|  | 100 | +        private void ResolveNonPrivateUDTFieldReferenceReplacements<T>(T field) where T: IEncapsulateFieldCandidate | 
|  | 101 | +        { | 
|  | 102 | +            foreach (var idRef in field.Declaration.References.Where(rf => !(rf.IsArrayAccess || rf.IsDefaultMemberAccess))) | 
|  | 103 | +            { | 
|  | 104 | +                var replacementExpression = MustAccessUsingBackingField(idRef, field) | 
|  | 105 | +                    ? GetBackingIdentifier(field) | 
|  | 106 | +                    : field.PropertyIdentifier; | 
|  | 107 | + | 
|  | 108 | +                if (RequiresModuleQualification(field, idRef, _declarationFinderProvider)) | 
|  | 109 | +                { | 
|  | 110 | +                    replacementExpression = $"{field.QualifiedModuleName.ComponentName}.{replacementExpression}"; | 
|  | 111 | +                } | 
|  | 112 | + | 
|  | 113 | +                AddIdentifierReplacement(idRef, idRef.Context, replacementExpression); | 
|  | 114 | +            } | 
|  | 115 | +        } | 
|  | 116 | + | 
|  | 117 | +        private static (ParserRuleContext, string) GenerateUDTMemberReplacementTuple<T>(T field, IdentifierReference rf, PropertyAttributeSet paSet) where T : IEncapsulateFieldCandidate | 
|  | 118 | +        { | 
|  | 119 | +            var replacementToken = paSet.PropertyName; | 
|  | 120 | +            if (rf.IsAssignment && field.IsReadOnly) | 
|  | 121 | +            { | 
|  | 122 | +                replacementToken = paSet.BackingField; | 
|  | 123 | +            } | 
|  | 124 | + | 
|  | 125 | +            switch (rf.Context.Parent) | 
|  | 126 | +            { | 
|  | 127 | +                case VBAParser.WithMemberAccessExprContext wmaec: | 
|  | 128 | +                    return (wmaec, rf.IsAssignment && field.IsReadOnly ? $".{paSet.PropertyName}" : paSet.PropertyName); | 
|  | 129 | +                case VBAParser.MemberAccessExprContext maec: | 
|  | 130 | +                    return (maec, rf.IsAssignment && field.IsReadOnly ? replacementToken : paSet.PropertyName); | 
|  | 131 | +                default: | 
|  | 132 | +                    return (rf.Context, replacementToken); | 
|  | 133 | +            } | 
|  | 134 | +        } | 
|  | 135 | + | 
|  | 136 | +        private static bool RequiresModuleQualification<T>(T field, IdentifierReference idRef, IDeclarationFinderProvider declarationFinderProvider) where T: IEncapsulateFieldCandidate | 
|  | 137 | +        { | 
|  | 138 | +            if (idRef.QualifiedModuleName == field.QualifiedModuleName) | 
|  | 139 | +            { | 
|  | 140 | +                return false; | 
|  | 141 | +            } | 
|  | 142 | + | 
|  | 143 | +            var isUDTField = field.Declaration.AsTypeDeclaration?.DeclarationType.HasFlag(DeclarationType.UserDefinedType) ?? false; | 
|  | 144 | + | 
|  | 145 | +            return (isUDTField && !EncapsulateFieldUtilities.IsModuleQualifiedExternalReferenceOfUDTField(declarationFinderProvider, idRef, field.QualifiedModuleName)) | 
|  | 146 | +                || !(idRef.Context.IsDescendentOf<VBAParser.MemberAccessExprContext>() || idRef.Context.IsDescendentOf<VBAParser.WithMemberAccessExprContext>()); | 
|  | 147 | +        } | 
|  | 148 | + | 
|  | 149 | +        private static bool IsInstanceOfPrivateUDT<T>(T field) where T: IEncapsulateFieldCandidate | 
|  | 150 | +        { | 
|  | 151 | +            bool IsPrivateUDT(IUserDefinedTypeCandidate u) => u.Declaration.AsTypeDeclaration.Accessibility == Accessibility.Private; | 
|  | 152 | +             | 
|  | 153 | +            return field is IEncapsulateFieldAsUDTMemberCandidate wrappedField | 
|  | 154 | +                ? wrappedField.WrappedCandidate is IUserDefinedTypeCandidate wrappedUDT && IsPrivateUDT(wrappedUDT) | 
|  | 155 | +                : field is IUserDefinedTypeCandidate udt && IsPrivateUDT(udt); | 
|  | 156 | +        } | 
|  | 157 | + | 
|  | 158 | +        private static bool MustAccessUsingBackingField(IdentifierReference rf, IEncapsulateFieldCandidate field) | 
|  | 159 | +            => rf.QualifiedModuleName == field.QualifiedModuleName | 
|  | 160 | +                && ((rf.IsAssignment && field.IsReadOnly) || field.Declaration.IsArray); | 
|  | 161 | + | 
|  | 162 | +        private static string GetBackingIdentifier(IEncapsulateFieldCandidate field) | 
|  | 163 | +        { | 
|  | 164 | +            var objStateUDT = field is IEncapsulateFieldAsUDTMemberCandidate udtM ? udtM.ObjectStateUDT : null; | 
|  | 165 | +            return objStateUDT is null | 
|  | 166 | +                ? field.BackingIdentifier | 
|  | 167 | +                : $"{objStateUDT.FieldIdentifier}.{field.BackingIdentifier}"; | 
|  | 168 | +        } | 
|  | 169 | + | 
|  | 170 | +        private void AddIdentifierReplacement(IdentifierReference idRef, ParserRuleContext context, string replacementText) | 
|  | 171 | +        { | 
|  | 172 | +            if (_identifierReplacements.ContainsKey(idRef)) | 
|  | 173 | +            { | 
|  | 174 | +                _identifierReplacements[idRef] = (context, replacementText); | 
|  | 175 | +                return; | 
|  | 176 | +            } | 
|  | 177 | +            _identifierReplacements.Add(idRef, (context, replacementText)); | 
|  | 178 | +        } | 
|  | 179 | +    } | 
|  | 180 | +} | 
0 commit comments