From a4574b29cbeec8073902ece6e1a97ba0c68a7b56 Mon Sep 17 00:00:00 2001 From: Christian Hornung Date: Fri, 20 Oct 2006 13:15:41 +0000 Subject: [PATCH] ResourceToolkit: Improved the performance of the resource resolvers (especially when working with large designer-generated localizable forms), mainly by increased caching of reusable information during a FindReferences run. Fixed finding a property<->field association when the return statement in the property getter includes a cast or a parenthesized expression. Fixed finding the resource manager when it is a field that is accessed through a property and this field is initialized directly in the declaraion. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@1917 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Project/ResourceToolkit.csproj | 2 + .../Resolver/BclNRefactoryResourceResolver.cs | 241 ++++++++++++++---- ...SharpCodeCoreNRefactoryResourceResolver.cs | 2 +- .../ICSharpCodeCoreResourceResolver.cs | 77 ++++-- .../Resolver/INRefactoryResourceResolver.cs | 2 +- .../Src/Resolver/MemberEqualityComparer.cs | 51 ++++ .../Src/Resolver/MemberFindAstVisitor.cs | 207 +++++++++++++++ .../Src/Resolver/NRefactoryAstCacheService.cs | 136 ++++++++++ .../Resolver/NRefactoryResourceResolver.cs | 33 ++- .../Resolver/PositionTrackingAstVisitor.cs | 41 ++- .../PropertyFieldAssociationVisitor.cs | 24 +- 11 files changed, 702 insertions(+), 114 deletions(-) create mode 100644 src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberEqualityComparer.cs create mode 100644 src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberFindAstVisitor.cs diff --git a/src/AddIns/Misc/ResourceToolkit/Project/ResourceToolkit.csproj b/src/AddIns/Misc/ResourceToolkit/Project/ResourceToolkit.csproj index 74b475ef81..4c8a52f323 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/ResourceToolkit.csproj +++ b/src/AddIns/Misc/ResourceToolkit/Project/ResourceToolkit.csproj @@ -91,6 +91,8 @@ + + diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs index d0d4a79a52..d3885a7833 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs @@ -15,6 +15,7 @@ using ICSharpCode.NRefactory.Ast; using ICSharpCode.NRefactory.Parser; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.SharpDevelop.Project; using Hornung.ResourceToolkit.ResourceFileContent; @@ -32,7 +33,7 @@ namespace Hornung.ResourceToolkit.Resolver /// Tries to find a resource reference in the specified expression. /// /// The ExpressionResult for the expression. - /// The AST representation of the expression. + /// The AST representation of the full expression. /// SharpDevelop's ResolveResult for the expression. /// The line where the expression is located. /// The column where the expression is located. @@ -46,13 +47,15 @@ namespace Hornung.ResourceToolkit.Resolver MemberResolveResult mrr = resolveResult as MemberResolveResult; if (mrr != null) { rfc = ResolveResourceFileContent(mrr.ResolvedMember); - } - - LocalResolveResult lrr = resolveResult as LocalResolveResult; - if (lrr != null) { - if (!lrr.IsParameter) { - rfc = ResolveResourceFileContent(lrr.Field); + } else { + + LocalResolveResult lrr = resolveResult as LocalResolveResult; + if (lrr != null) { + if (!lrr.IsParameter) { + rfc = ResolveResourceFileContent(lrr.Field); + } } + } @@ -68,6 +71,26 @@ namespace Hornung.ResourceToolkit.Resolver // ******************************************************************************************************************************** + #region ResourceFileContent mapping cache + + static Dictionary cachedResourceFileContentMappings; + + static BclNRefactoryResourceResolver() + { + cachedResourceFileContentMappings = new Dictionary(new MemberEqualityComparer()); + NRefactoryAstCacheService.CacheEnabledChanged += NRefactoryCacheEnabledChanged; + } + + static void NRefactoryCacheEnabledChanged(object sender, EventArgs e) + { + if (!NRefactoryAstCacheService.CacheEnabled) { + // Clear cache when disabled. + cachedResourceFileContentMappings.Clear(); + } + } + + #endregion + /// /// Tries to determine the resource file content which is referenced by the /// resource manager which is assigned to the specified member. @@ -79,33 +102,49 @@ namespace Hornung.ResourceToolkit.Resolver /// static IResourceFileContent ResolveResourceFileContent(IMember member) { - if (member != null && member.ReturnType != null) { - if (IsResourceManager(member.ReturnType) && member.DeclaringType != null && member.DeclaringType.CompilationUnit != null) { + if (member != null && member.ReturnType != null && + member.DeclaringType != null && member.DeclaringType.CompilationUnit != null) { + + IResourceFileContent content; + if (!NRefactoryAstCacheService.CacheEnabled || !cachedResourceFileContentMappings.TryGetValue(member, out content)) { string declaringFileName = member.DeclaringType.CompilationUnit.FileName; if (declaringFileName != null) { - - SupportedLanguage? language = NRefactoryResourceResolver.GetFileLanguage(declaringFileName); - if (language == null) { - return null; - } - - CompilationUnit cu = NRefactoryAstCacheService.GetFullAst(language.Value, declaringFileName); - if (cu != null) { + if (IsResourceManager(member.ReturnType, declaringFileName)) { + + SupportedLanguage? language = NRefactoryResourceResolver.GetFileLanguage(declaringFileName); + if (language == null) { + return null; + } - ResourceManagerInitializationFindVisitor visitor = new ResourceManagerInitializationFindVisitor(member); - cu.AcceptVisitor(visitor, null); - if (visitor.FoundFileName != null) { + CompilationUnit cu = NRefactoryAstCacheService.GetFullAst(language.Value, declaringFileName); + if (cu != null) { - return ResourceFileContentRegistry.GetResourceFileContent(visitor.FoundFileName); + ResourceManagerInitializationFindVisitor visitor = new ResourceManagerInitializationFindVisitor(member); + cu.AcceptVisitor(visitor, null); + if (visitor.FoundFileName != null) { + + content = ResourceFileContentRegistry.GetResourceFileContent(visitor.FoundFileName); + + if (NRefactoryAstCacheService.CacheEnabled && content != null) { + cachedResourceFileContentMappings.Add(member, content); + } + + return content; + + } } } - } + return null; + } + + return content; + } return null; } @@ -114,9 +153,21 @@ namespace Hornung.ResourceToolkit.Resolver /// Determines if the specified type is a ResourceManager type that can /// be handled by this resolver. /// - static bool IsResourceManager(IReturnType type) + static bool IsResourceManager(IReturnType type, string sourceFileName) { - IClass resourceManager = ParserService.CurrentProjectContent.GetClass("System.Resources.ResourceManager"); + IProject p = ProjectFileDictionaryService.GetProjectForFile(sourceFileName); + IProjectContent pc; + if (p == null) { + pc = ParserService.CurrentProjectContent; + } else { + pc = ParserService.GetProjectContent(p); + } + + if (pc == null) { + return false; + } + + IClass resourceManager = pc.GetClass("System.Resources.ResourceManager"); if (resourceManager == null) { return false; } @@ -144,6 +195,9 @@ namespace Hornung.ResourceToolkit.Resolver readonly IMember resourceManagerMember; readonly bool isLocalVariable; + bool triedToResolvePropertyAssociation; + IField resourceManagerFieldAccessedByProperty; + CompilationUnit compilationUnit; string foundFileName; @@ -188,6 +242,12 @@ namespace Hornung.ResourceToolkit.Resolver public override object TrackedVisit(VariableDeclaration variableDeclaration, object data) { + // Resolving anything here only makes sense + // if this declaration actually has an initializer. + if (variableDeclaration.Initializer.IsNull) { + return base.TrackedVisit(variableDeclaration, data); + } + LocalVariableDeclaration localVariableDeclaration = data as LocalVariableDeclaration; if (this.isLocalVariable && localVariableDeclaration != null) { if (variableDeclaration.Name == this.resourceManagerMember.Name) { @@ -202,20 +262,42 @@ namespace Hornung.ResourceToolkit.Resolver } } + FieldDeclaration fieldDeclaration = data as FieldDeclaration; if (!this.isLocalVariable && fieldDeclaration != null) { - if (variableDeclaration.Name == this.resourceManagerMember.Name) { - // Make sure we got the right declaration by comparing the positions. - // Both must have the same start position. - if (fieldDeclaration.StartLocation.X == this.resourceManagerMember.Region.BeginColumn && fieldDeclaration.StartLocation.Y == this.resourceManagerMember.Region.BeginLine) { + // Make sure we got the right declaration by comparing the positions. + // Both must have the same start position. + if (variableDeclaration.Name == this.resourceManagerMember.Name && + fieldDeclaration.StartLocation.X == this.resourceManagerMember.Region.BeginColumn && fieldDeclaration.StartLocation.Y == this.resourceManagerMember.Region.BeginLine) { + + #if DEBUG + LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found field declaration: "+fieldDeclaration.ToString()+" at "+fieldDeclaration.StartLocation.ToString()); + #endif + data = true; + + } else { + + // This field might be referred to by a property + // that we are looking for. + // This association is cached in the + // resourceManagerFieldAccessedByProperty field + // to improve performance. + this.TryResolveResourceManagerProperty(); + + if (this.resourceManagerFieldAccessedByProperty != null && + fieldDeclaration.StartLocation.X == this.resourceManagerFieldAccessedByProperty.Region.BeginColumn && + fieldDeclaration.StartLocation.Y == this.resourceManagerFieldAccessedByProperty.Region.BeginLine) { + #if DEBUG - LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found field declaration: "+fieldDeclaration.ToString()+" at "+fieldDeclaration.StartLocation.ToString()); + LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found field declaration (via associated property): "+fieldDeclaration.ToString()+" at "+fieldDeclaration.StartLocation.ToString()); #endif data = true; + } } } + return base.TrackedVisit(variableDeclaration, data); } @@ -226,15 +308,29 @@ namespace Hornung.ResourceToolkit.Resolver (!this.isLocalVariable || this.resourceManagerMember.Region.IsInside(this.CurrentNodeStartLocation.Y, this.CurrentNodeStartLocation.X)) // skip if local variable is out of scope ) { - MemberResolveResult mrr = this.Resolve(assignmentExpression.Left, this.resourceManagerMember) as MemberResolveResult; - if (mrr != null) { + IMember resolvedMember = null; + ResolveResult rr = this.Resolve(assignmentExpression.Left, this.resourceManagerMember.DeclaringType.CompilationUnit.FileName); + if (rr != null) { + // Support both local variables and member variables + MemberResolveResult mrr = rr as MemberResolveResult; + if (mrr != null) { + resolvedMember = mrr.ResolvedMember; + } else { + LocalResolveResult lrr = rr as LocalResolveResult; + if (lrr != null) { + resolvedMember = lrr.Field; + } + } + } + + if (resolvedMember != null) { #if DEBUG - LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver: Resolved member: "+mrr.ResolvedMember.ToString()); + LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver: Resolved member: "+resolvedMember.ToString()); #endif // HACK: The GetType()s are necessary because the DOM IComparable implementations try to cast the parameter object to their own interface type which may fail. - if (mrr.ResolvedMember.GetType().Equals(this.resourceManagerMember.GetType()) && mrr.ResolvedMember.CompareTo(this.resourceManagerMember) == 0) { + if (resolvedMember.GetType().Equals(this.resourceManagerMember.GetType()) && resolvedMember.CompareTo(this.resourceManagerMember) == 0) { #if DEBUG LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found assignment to field: "+assignmentExpression.ToString()); @@ -245,35 +341,28 @@ namespace Hornung.ResourceToolkit.Resolver // there is a possible relationship between the return types // of the resolved member and the member we are looking for. } else if (this.compilationUnit != null && !this.isLocalVariable && - ( - mrr.ResolvedMember.ReturnType.Equals(this.resourceManagerMember.ReturnType) || - ( - mrr.ResolvedMember.ReturnType.GetUnderlyingClass() != null && this.resourceManagerMember.ReturnType.GetUnderlyingClass() != null && - ( - mrr.ResolvedMember.ReturnType.GetUnderlyingClass().IsTypeInInheritanceTree(this.resourceManagerMember.ReturnType.GetUnderlyingClass()) || - this.resourceManagerMember.ReturnType.GetUnderlyingClass().IsTypeInInheritanceTree(mrr.ResolvedMember.ReturnType.GetUnderlyingClass()) - ) - ) - )) { + IsTypeRelationshipPossible(resolvedMember, this.resourceManagerMember)) { - if (this.resourceManagerMember is IProperty && mrr.ResolvedMember is IField) { + if (this.resourceManagerMember is IProperty && resolvedMember is IField) { // Find out if the resourceManagerMember is a property whose get block returns the value of the resolved member. - PropertyFieldAssociationVisitor visitor = new PropertyFieldAssociationVisitor((IProperty)this.resourceManagerMember); - this.compilationUnit.AcceptVisitor(visitor, null); - if (visitor.AssociatedField != null && visitor.AssociatedField.CompareTo(mrr.ResolvedMember) == 0) { + // We might already have found this association in the + // resourceManagerFieldAccessedByProperty field. + this.TryResolveResourceManagerProperty(); + + if (this.resourceManagerFieldAccessedByProperty != null && this.resourceManagerFieldAccessedByProperty.CompareTo(resolvedMember) == 0) { #if DEBUG LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found assignment to field: "+assignmentExpression.ToString()); #endif data = true; } - } else if (this.resourceManagerMember is IField && mrr.ResolvedMember is IProperty) { + } else if (this.resourceManagerMember is IField && resolvedMember is IProperty) { // Find out if the resolved member is a property whose set block assigns the value to the resourceManagerMember. PropertyFieldAssociationVisitor visitor = new PropertyFieldAssociationVisitor((IField)this.resourceManagerMember); this.compilationUnit.AcceptVisitor(visitor, null); - if (visitor.AssociatedProperty != null && visitor.AssociatedProperty.CompareTo(mrr.ResolvedMember) == 0) { + if (visitor.AssociatedProperty != null && visitor.AssociatedProperty.CompareTo(resolvedMember) == 0) { #if DEBUG LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found assignment to property: "+assignmentExpression.ToString()); #endif @@ -290,6 +379,35 @@ namespace Hornung.ResourceToolkit.Resolver return base.TrackedVisit(assignmentExpression, data); } + /// + /// If the resourceManagerMember is a property, this method tries + /// to find the field that this property is associated to. + /// This association is cached in the + /// resourceManagerFieldAccessedByProperty field + /// to improve performance. + /// + void TryResolveResourceManagerProperty() + { + if (this.resourceManagerFieldAccessedByProperty == null && !this.triedToResolvePropertyAssociation) { + + // Don't try this more than once in the same CompilationUnit + this.triedToResolvePropertyAssociation = true; + + IProperty prop = this.resourceManagerMember as IProperty; + if (prop != null) { + + // Resolve the property association. + PropertyFieldAssociationVisitor visitor = new PropertyFieldAssociationVisitor(prop); + this.compilationUnit.AcceptVisitor(visitor, null); + + // Store the association in the instance field. + this.resourceManagerFieldAccessedByProperty = visitor.AssociatedField; + + } + + } + } + public override object TrackedVisit(ObjectCreateExpression objectCreateExpression, object data) { if (data as bool? ?? false) { @@ -300,7 +418,7 @@ namespace Hornung.ResourceToolkit.Resolver // Resolve the constructor. // A type derived from the declaration type is also allowed. - MemberResolveResult mrr = this.Resolve(objectCreateExpression, this.resourceManagerMember) as MemberResolveResult; + MemberResolveResult mrr = this.Resolve(objectCreateExpression, this.resourceManagerMember.DeclaringType.CompilationUnit.FileName) as MemberResolveResult; #if DEBUG if (mrr != null) { @@ -345,7 +463,7 @@ namespace Hornung.ResourceToolkit.Resolver // Support typeof(...) TypeOfExpression t = param as TypeOfExpression; if (t != null && this.PositionAvailable) { - TypeResolveResult trr = this.Resolve(new TypeReferenceExpression(t.TypeReference), this.resourceManagerMember) as TypeResolveResult; + TypeResolveResult trr = this.Resolve(new TypeReferenceExpression(t.TypeReference), this.resourceManagerMember.DeclaringType.CompilationUnit.FileName) as TypeResolveResult; if (trr != null) { #if DEBUG @@ -377,6 +495,27 @@ namespace Hornung.ResourceToolkit.Resolver #endregion + /// + /// Determines whether there is a possible relationship between the + /// return types of member1 and member2. + /// + public static bool IsTypeRelationshipPossible(IMember member1, IMember member2) + { + if (member1.ReturnType.Equals(member2.ReturnType)) { + return true; + } + IClass class1; + IClass class2; + if ((class1 = member1.ReturnType.GetUnderlyingClass()) == null) { + return false; + } + if ((class2 = member2.ReturnType.GetUnderlyingClass()) == null) { + return false; + } + return class1.IsTypeInInheritanceTree(class2) || + class2.IsTypeInInheritanceTree(class1); + } + // ******************************************************************************************************************************** /// diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs index d6bb2abc6b..daf03ef540 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs @@ -28,7 +28,7 @@ namespace Hornung.ResourceToolkit.Resolver /// Tries to find a resource reference in the specified expression. /// /// The ExpressionResult for the expression. - /// The AST representation of the expression. + /// The AST representation of the full expression. /// SharpDevelop's ResolveResult for the expression. /// The line where the expression is located. /// The column where the expression is located. diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs index f2ce0fb240..07c471293f 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs @@ -207,13 +207,20 @@ namespace Hornung.ResourceToolkit.Resolver return null; } - string localFile; - foreach (string relativePath in AddInTree.BuildItems("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/LocalResourcesLocations", null, false)) { - if ((localFile = FindICSharpCodeCoreResourceFile(Path.GetFullPath(Path.Combine(project.Directory, relativePath)))) != null) { - return localFile; + string localFile = null; + + if (!NRefactoryAstCacheService.CacheEnabled || !cachedLocalResourceFiles.TryGetValue(project, out localFile)) { + foreach (string relativePath in AddInTree.BuildItems("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/LocalResourcesLocations", null, false)) { + if ((localFile = FindICSharpCodeCoreResourceFile(Path.GetFullPath(Path.Combine(project.Directory, relativePath)))) != null) { + if (NRefactoryAstCacheService.CacheEnabled) { + cachedLocalResourceFiles.Add(project, localFile); + } + break; + } } } - return null; + + return localFile; } /// @@ -223,37 +230,45 @@ namespace Hornung.ResourceToolkit.Resolver public static string GetICSharpCodeCoreHostResourceFileName(string sourceFileName) { IProject project = ProjectFileDictionaryService.GetProjectForFile(sourceFileName); + string hostFile = null; - // Get SD directory using the reference to ICSharpCode.Core - string coreAssemblyFullPath = GetICSharpCodeCoreFullPath(project); - - if (coreAssemblyFullPath == null) { - // Look for the ICSharpCode.Core project using all available projects. - if (ProjectService.OpenSolution != null) { - foreach (IProject p in ProjectService.OpenSolution.Projects) { - if ((coreAssemblyFullPath = GetICSharpCodeCoreFullPath(p)) != null) { - break; + if (project == null || + !NRefactoryAstCacheService.CacheEnabled || !cachedHostResourceFiles.TryGetValue(project, out hostFile)) { + + // Get SD directory using the reference to ICSharpCode.Core + string coreAssemblyFullPath = GetICSharpCodeCoreFullPath(project); + + if (coreAssemblyFullPath == null) { + // Look for the ICSharpCode.Core project using all available projects. + if (ProjectService.OpenSolution != null) { + foreach (IProject p in ProjectService.OpenSolution.Projects) { + if ((coreAssemblyFullPath = GetICSharpCodeCoreFullPath(p)) != null) { + break; + } } } } - } - - if (coreAssemblyFullPath != null) { + + if (coreAssemblyFullPath == null) { + return null; + } #if DEBUG LoggingService.Debug("ResourceToolkit: ICSharpCodeCoreResourceResolver coreAssemblyFullPath = "+coreAssemblyFullPath); #endif - string hostFile; foreach (string relativePath in AddInTree.BuildItems("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/HostResourcesLocations", null, false)) { if ((hostFile = FindICSharpCodeCoreResourceFile(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(coreAssemblyFullPath), relativePath)))) != null) { - return hostFile; + if (NRefactoryAstCacheService.CacheEnabled && project != null) { + cachedHostResourceFiles.Add(project, hostFile); + } + break; } } } - return null; + return hostFile; } static string GetICSharpCodeCoreFullPath(IProject sourceProject) @@ -296,5 +311,27 @@ namespace Hornung.ResourceToolkit.Resolver return coreAssemblyFullPath; } + #region ICSharpCode.Core resource file mapping cache + + static Dictionary cachedLocalResourceFiles; + static Dictionary cachedHostResourceFiles; + + static ICSharpCodeCoreResourceResolver() + { + cachedLocalResourceFiles = new Dictionary(); + cachedHostResourceFiles = new Dictionary(); + NRefactoryAstCacheService.CacheEnabledChanged += NRefactoryCacheEnabledChanged; + } + + static void NRefactoryCacheEnabledChanged(object sender, EventArgs e) + { + if (!NRefactoryAstCacheService.CacheEnabled) { + // Clear cache when disabled. + cachedLocalResourceFiles.Clear(); + cachedHostResourceFiles.Clear(); + } + } + + #endregion } } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs index 986999e35a..7c34db09c5 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs @@ -24,7 +24,7 @@ namespace Hornung.ResourceToolkit.Resolver /// Tries to find a resource reference in the specified expression. /// /// The ExpressionResult for the expression. - /// The AST representation of the expression. + /// The AST representation of the full expression. /// SharpDevelop's ResolveResult for the expression. /// The line where the expression is located. /// The column where the expression is located. diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberEqualityComparer.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberEqualityComparer.cs new file mode 100644 index 0000000000..6db57e6fad --- /dev/null +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberEqualityComparer.cs @@ -0,0 +1,51 @@ +// +// +// +// +// $Revision$ +// + +using System; +using System.Collections.Generic; + +using ICSharpCode.SharpDevelop.Dom; + +namespace Hornung.ResourceToolkit.Resolver +{ + /// + /// Determines equality of DOM members by region and fully qualified name. + /// + public class MemberEqualityComparer : IEqualityComparer + { + public bool Equals(IMember x, IMember y) + { + if (x == null || y == null) { + return false; + } + if (x.Region.CompareTo(y.Region) != 0) { + return false; + } + IComparer nameComparer; + if (x.DeclaringType != null && + x.DeclaringType.ProjectContent != null && + x.DeclaringType.ProjectContent.Language != null) { + nameComparer = x.DeclaringType.ProjectContent.Language.NameComparer; + } else { + nameComparer = StringComparer.InvariantCulture; + } + return nameComparer.Compare(x.FullyQualifiedName, y.FullyQualifiedName) == 0; + } + + public int GetHashCode(IMember obj) + { + if (obj == null) { + return 0; + } + return obj.Region.BeginLine ^ + obj.Region.BeginColumn ^ + obj.Region.EndLine ^ + obj.Region.EndColumn ^ + obj.FullyQualifiedName.GetHashCode(); + } + } +} diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberFindAstVisitor.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberFindAstVisitor.cs new file mode 100644 index 0000000000..c4a047ac6f --- /dev/null +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberFindAstVisitor.cs @@ -0,0 +1,207 @@ +// +// +// +// +// $Revision$ +// + +using System; + +using ICSharpCode.NRefactory.Ast; +using ICSharpCode.NRefactory.Visitors; +using ICSharpCode.SharpDevelop.Dom; + +namespace Hornung.ResourceToolkit.Resolver +{ + /// + /// Finds a certain member inside the AST. + /// + public class MemberFindAstVisitor : AbstractAstVisitor + { + readonly IMember memberToFind; + INode memberNode; + + /// + /// Initializes a new instance of the class. + /// + /// The member to find. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters", MessageId = "System.ArgumentException.#ctor(System.String,System.String)")] + public MemberFindAstVisitor(IMember member) + { + if (member == null) { + throw new ArgumentNullException("member"); + } + if (member.Region.IsEmpty) { + throw new ArgumentException("Cannot find this member because its region is empty."+Environment.NewLine+"member: '"+member.ToString()+"'", "member"); + } + this.memberToFind = member; + } + + /// + /// Gets the INode that belongs to the member specified in the constructor call, + /// or a null reference, if the member cannot be found. + /// + public INode MemberNode { + get { + return this.memberNode; + } + } + + // ******************************************************************************************************************************** + + /// + /// Tests whether the specified node is the node belonging to the member we are + /// looking for. + /// + /// true, if this is the right node or the node has already been found before, otherwise false. + bool CheckNode(INode node) + { + if (this.memberNode != null) { + return true; + } + if (!node.StartLocation.IsEmpty && + node.StartLocation.Y == this.memberToFind.Region.BeginLine && + node.StartLocation.X == this.memberToFind.Region.BeginColumn) { + this.memberNode = node; + return true; + } + return false; + } + + public override object VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration, object data) + { + if (this.CheckNode(constructorDeclaration)) { + return null; + } + return base.VisitConstructorDeclaration(constructorDeclaration, data); + } + + public override object VisitDeclareDeclaration(DeclareDeclaration declareDeclaration, object data) + { + if (this.CheckNode(declareDeclaration)) { + return null; + } + return base.VisitDeclareDeclaration(declareDeclaration, data); + } + + public override object VisitDelegateDeclaration(DelegateDeclaration delegateDeclaration, object data) + { + if (this.CheckNode(delegateDeclaration)) { + return null; + } + return base.VisitDelegateDeclaration(delegateDeclaration, data); + } + + public override object VisitDestructorDeclaration(DestructorDeclaration destructorDeclaration, object data) + { + if (this.CheckNode(destructorDeclaration)) { + return null; + } + return base.VisitDestructorDeclaration(destructorDeclaration, data); + } + + public override object VisitEventDeclaration(EventDeclaration eventDeclaration, object data) + { + if (this.CheckNode(eventDeclaration)) { + return null; + } + return base.VisitEventDeclaration(eventDeclaration, data); + } + + public override object VisitFieldDeclaration(FieldDeclaration fieldDeclaration, object data) + { + if (this.CheckNode(fieldDeclaration)) { + return null; + } + return base.VisitFieldDeclaration(fieldDeclaration, data); + } + + public override object VisitIndexerDeclaration(IndexerDeclaration indexerDeclaration, object data) + { + if (this.CheckNode(indexerDeclaration)) { + return null; + } + return base.VisitIndexerDeclaration(indexerDeclaration, data); + } + + public override object VisitLocalVariableDeclaration(LocalVariableDeclaration localVariableDeclaration, object data) + { + if (this.CheckNode(localVariableDeclaration)) { + return null; + } + return base.VisitLocalVariableDeclaration(localVariableDeclaration, data); + } + + public override object VisitMethodDeclaration(MethodDeclaration methodDeclaration, object data) + { + if (this.CheckNode(methodDeclaration)) { + return null; + } + return base.VisitMethodDeclaration(methodDeclaration, data); + } + + public override object VisitNamespaceDeclaration(NamespaceDeclaration namespaceDeclaration, object data) + { + if (this.CheckNode(namespaceDeclaration)) { + return null; + } + return base.VisitNamespaceDeclaration(namespaceDeclaration, data); + } + + public override object VisitOperatorDeclaration(OperatorDeclaration operatorDeclaration, object data) + { + if (this.CheckNode(operatorDeclaration)) { + return null; + } + return base.VisitOperatorDeclaration(operatorDeclaration, data); + } + + public override object VisitParameterDeclarationExpression(ParameterDeclarationExpression parameterDeclarationExpression, object data) + { + if (this.CheckNode(parameterDeclarationExpression)) { + return null; + } + return base.VisitParameterDeclarationExpression(parameterDeclarationExpression, data); + } + + public override object VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration, object data) + { + if (this.CheckNode(propertyDeclaration)) { + return null; + } + return base.VisitPropertyDeclaration(propertyDeclaration, data); + } + + public override object VisitPropertyGetRegion(PropertyGetRegion propertyGetRegion, object data) + { + if (this.CheckNode(propertyGetRegion)) { + return null; + } + return base.VisitPropertyGetRegion(propertyGetRegion, data); + } + + public override object VisitPropertySetRegion(PropertySetRegion propertySetRegion, object data) + { + if (this.CheckNode(propertySetRegion)) { + return null; + } + return base.VisitPropertySetRegion(propertySetRegion, data); + } + + public override object VisitTypeDeclaration(TypeDeclaration typeDeclaration, object data) + { + if (this.CheckNode(typeDeclaration)) { + return null; + } + return base.VisitTypeDeclaration(typeDeclaration, data); + } + + public override object VisitVariableDeclaration(VariableDeclaration variableDeclaration, object data) + { + if (this.CheckNode(variableDeclaration)) { + return null; + } + return base.VisitVariableDeclaration(variableDeclaration, data); + } + } +} diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs index 98349b7b43..e667671b32 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs @@ -14,16 +14,23 @@ using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.Ast; using ICSharpCode.NRefactory.Parser; using ICSharpCode.SharpDevelop; +using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver; +using ICSharpCode.SharpDevelop.Project; namespace Hornung.ResourceToolkit.Resolver { /// /// Parses files using NRefactory and caches the AST on demand. + /// Provides an event to monitor the cache status and + /// the method to resolve expressions + /// faster by making use of the cache. /// public static class NRefactoryAstCacheService { static bool cacheEnabled; static Dictionary cachedAstInfo = new Dictionary(); + static Dictionary cachedMemberMappings = new Dictionary(new MemberEqualityComparer()); /// /// Gets a flag that indicates whether the AST cache is currently enabled. @@ -34,6 +41,21 @@ namespace Hornung.ResourceToolkit.Resolver } } + /// + /// Occurs when the cache is enabled or disabled. + /// + public static event EventHandler CacheEnabledChanged; + + /// + /// Raises the CacheEnabledChanged event. + /// + private static void OnCacheEnabledChanged(EventArgs e) + { + if (CacheEnabledChanged != null) { + CacheEnabledChanged(null, e); + } + } + /// /// Enables the AST cache. /// @@ -46,6 +68,7 @@ namespace Hornung.ResourceToolkit.Resolver } cacheEnabled = true; LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService cache enabled"); + OnCacheEnabledChanged(EventArgs.Empty); } /// @@ -55,7 +78,9 @@ namespace Hornung.ResourceToolkit.Resolver { cacheEnabled = false; cachedAstInfo.Clear(); + cachedMemberMappings.Clear(); LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService cache disabled and cleared"); + OnCacheEnabledChanged(EventArgs.Empty); } /// @@ -91,5 +116,116 @@ namespace Hornung.ResourceToolkit.Resolver } return null; } + + // ******************************************************************************************************************************** + + /// + /// Resolves an expression using low-level NRefactoryResolver methods and making + /// use of the cache if possible. + /// + /// The file name of the source code file that contains the expression to be resolved. + /// The 1-based line number of the expression. + /// The 1-based column number of the expression. + /// The CompilationUnit that contains the NRefactory AST for this file. May be null (then the CompilationUnit is retrieved from the cache or the file is parsed). + /// The expression to be resolved. + /// The ExpressionContext of the expression. + /// A ResolveResult or null if the expression cannot be resolved. + public static ResolveResult ResolveLowLevel(string fileName, int caretLine, int caretColumn, CompilationUnit compilationUnit, string expression, ExpressionContext context) + { + using (ICSharpCode.NRefactory.IParser p = ICSharpCode.NRefactory.ParserFactory.CreateParser(NRefactoryResourceResolver.GetFileLanguage(fileName).Value, new System.IO.StringReader(expression))) { + Expression expr = p.ParseExpression(); + if (expr == null) { + return null; + } + return ResolveLowLevel(fileName, caretLine, caretColumn, compilationUnit, expression, expr, context); + } + } + + /// + /// Resolves an expression using low-level NRefactoryResolver methods and making + /// use of the cache if possible. + /// + /// The file name of the source code file that contains the expression to be resolved. + /// The 1-based line number of the expression. + /// The 1-based column number of the expression. + /// The CompilationUnit that contains the NRefactory AST for this file. May be null (then the CompilationUnit is retrieved from the cache or the file is parsed). + /// The expression to be resolved as a string. If this parameter is null, the expression string is generated by using the code generator. + /// The parsed expression to be resolved. + /// The ExpressionContext of the expression. + /// A ResolveResult or null if the expression cannot be resolved. + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1720:AvoidTypeNamesInParameters", MessageId = "4#")] + public static ResolveResult ResolveLowLevel(string fileName, int caretLine, int caretColumn, CompilationUnit compilationUnit, string expressionString, Expression expression, ExpressionContext context) + { + if (fileName == null) { + throw new ArgumentNullException("fileName"); + } + if (expression == null) { + throw new ArgumentNullException("expression"); + } + + IProject p = ProjectFileDictionaryService.GetProjectForFile(fileName); + if (p == null) { + LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService: ResolveLowLevel failed. Project is null for file '"+fileName+"'"); + return null; + } + + IProjectContent pc = ParserService.GetProjectContent(p); + if (pc == null) { + LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService: ResolveLowLevel failed. ProjectContent is null for project '"+p.ToString()+"'"); + return null; + } + + NRefactoryResolver resolver = new NRefactoryResolver(pc); + + if (compilationUnit == null) { + compilationUnit = GetFullAst(resolver.Language, fileName); + } + if (compilationUnit == null) { + LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService: ResolveLowLevel failed due to the compilation unit being unavailable."); + return null; + } + + if (!resolver.Initialize(fileName, caretLine, caretColumn)) { + LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService: ResolveLowLevel failed. NRefactoryResolver.Initialize returned false."); + return null; + } + + if (resolver.CallingClass != null) { + ResolveResult rr; + if (expressionString == null) { + // HACK: Re-generate the code for the expression from the expression object by using the code generator. + // This is necessary when invoking from inside an AST visitor where the + // code belonging to this expression is unavailable. + expressionString = resolver.LanguageProperties.CodeGenerator.GenerateCode(expression, String.Empty); + } + if ((rr = NRefactoryResolver.GetResultFromDeclarationLine(resolver.CallingClass, resolver.CallingMember as IMethodOrProperty, caretLine, caretColumn, expressionString)) != null) { + return rr; + } + } + + if (resolver.CallingMember != null) { + + // Cache member->node mappings to improves performance + // (if cache is enabled) + INode memberNode; + if (!CacheEnabled || !cachedMemberMappings.TryGetValue(resolver.CallingMember, out memberNode)) { + MemberFindAstVisitor visitor = new MemberFindAstVisitor(resolver.CallingMember); + compilationUnit.AcceptVisitor(visitor, null); + memberNode = visitor.MemberNode; + if (CacheEnabled && memberNode != null) { + cachedMemberMappings.Add(resolver.CallingMember, memberNode); + } + } + + if (memberNode == null) { + LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService: Could not find member in AST: "+resolver.CallingMember.ToString()); + } else { + resolver.RunLookupTableVisitor(memberNode); + } + + } + + return resolver.ResolveInternal(expression, context); + } } } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs index 51a77c8478..e325b5a7c2 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs @@ -128,23 +128,30 @@ namespace Hornung.ResourceToolkit.Resolver ResourceResolveResult rrr = null; - // The full expression is parsed here because - // we will need to modify the result in the next step. - Expression expr = null; SupportedLanguage? language = GetFileLanguage(fileName); - if (language != null) { - using(ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser(language.Value, new StringReader(result.Expression))) { - if (parser != null) { - expr = parser.ParseExpression(); - } - } + if (language == null) { + return null; } // The resolve routine needs the member which contains the actual member being referenced. // If a complete expression is given, the expression needs to be reduced to // the member reference. + Expression fullExpr = null; while (result.Expression != null && result.Expression.Length > 0) { - if ((rrr = TryResolve(result, expr, caretLine, caretColumn, fileName, document.TextContent)) != null) { + Expression expr = null; + using(ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser(language.Value, new StringReader(result.Expression))) { + if (parser != null) { + expr = parser.ParseExpression(); + } + } + + if (expr == null) { + break; + } + if (fullExpr == null) { + fullExpr = expr; + } + if ((rrr = TryResolve(result, expr, fullExpr, caretLine, caretColumn, fileName, document.TextContent)) != null) { break; } result.Expression = ef.RemoveLastPart(result.Expression); @@ -163,14 +170,14 @@ namespace Hornung.ResourceToolkit.Resolver /// Tries to resolve the resource reference using all available /// NRefactory resource resolvers. /// - static ResourceResolveResult TryResolve(ExpressionResult result, Expression expr, int caretLine, int caretColumn, string fileName, string fileContent) + static ResourceResolveResult TryResolve(ExpressionResult result, Expression expr, Expression fullExpr, int caretLine, int caretColumn, string fileName, string fileContent) { - ResolveResult rr = ParserService.Resolve(result, caretLine, caretColumn, fileName, fileContent); + ResolveResult rr = NRefactoryAstCacheService.ResolveLowLevel(fileName, caretLine+1, caretColumn+1, null, result.Expression, expr, result.Context); if (rr != null) { ResourceResolveResult rrr; foreach (INRefactoryResourceResolver resolver in Resolvers) { - if ((rrr = resolver.Resolve(result, expr, rr, caretLine, caretColumn, fileName, fileContent)) != null) { + if ((rrr = resolver.Resolve(result, fullExpr, rr, caretLine, caretColumn, fileName, fileContent)) != null) { return rrr; } } diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PositionTrackingAstVisitor.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PositionTrackingAstVisitor.cs index 1c0676a0d7..9ff675c6a7 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PositionTrackingAstVisitor.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PositionTrackingAstVisitor.cs @@ -82,40 +82,33 @@ namespace Hornung.ResourceToolkit.Resolver // ******************************************************************************************************************************** + private CompilationUnit compilationUnit; + + public override object TrackedVisit(CompilationUnit compilationUnit, object data) + { + this.compilationUnit = compilationUnit; + return base.TrackedVisit(compilationUnit, data); + } + + // ******************************************************************************************************************************** + /// /// Resolves an expression in the current node's context. /// /// The expression to be resolved. - /// Any member declared in the source file in question. Used to get the language, file name and file content. - public ResolveResult Resolve(Expression expression, IMember memberInThisFile) + /// The file name of the source file that contains the expression to be resolved. + public ResolveResult Resolve(Expression expression, string fileName) { if (!this.PositionAvailable) { - LoggingService.Debug("ResourceToolkit: PositionTrackingAstVisitor: Resolve failed due to position information being unavailable. Expression: "+expression.ToString()); + LoggingService.Info("ResourceToolkit: PositionTrackingAstVisitor: Resolve failed due to position information being unavailable. Expression: "+expression.ToString()); return null; } - // In order to resolve expression, we need the original code. - // HACK: To get the code belonging to this expression, we pass it through the code generator. - // (Is there a better way?) - - string code = null; - LanguageProperties lp = NRefactoryResourceResolver.GetLanguagePropertiesForMember(memberInThisFile); - if (lp != null && lp.CodeGenerator != null) { - code = lp.CodeGenerator.GenerateCode(expression, String.Empty); - } - - if (!String.IsNullOrEmpty(code)) { - - // Now resolve the expression in the current context. - ResolveResult rr = ParserService.Resolve(new ExpressionResult(code, ExpressionContext.Default), this.CurrentNodeStartLocation.Y-1, this.CurrentNodeStartLocation.X, memberInThisFile.DeclaringType.CompilationUnit.FileName, ParserService.GetParseableFileContent(memberInThisFile.DeclaringType.CompilationUnit.FileName)); - - return rr; - - } else { - LoggingService.Debug("ResourceToolkit: PositionTrackingAstVisitor could not re-generate code for the expression: "+expression.ToString()); - } + #if DEBUG + LoggingService.Debug("ResourceToolkit: PositionTrackingAstVisitor: Using this parent node for resolve: "+this.parentNodes.Peek().ToString()); + #endif - return null; + return NRefactoryAstCacheService.ResolveLowLevel(fileName, this.CurrentNodeStartLocation.Y, this.CurrentNodeStartLocation.X+1, this.compilationUnit, null, expression, ExpressionContext.Default); } // ******************************************************************************************************************************** diff --git a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PropertyFieldAssociationVisitor.cs b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PropertyFieldAssociationVisitor.cs index 7f0ff3c8dc..10d1a3a6d0 100644 --- a/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PropertyFieldAssociationVisitor.cs +++ b/src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PropertyFieldAssociationVisitor.cs @@ -100,8 +100,24 @@ namespace Hornung.ResourceToolkit.Resolver if (this.associatedMember == null && // skip if already found to improve performance this.currentContext == VisitorContext.PropertyGetRegion && data != null) { + // Fix some type casting and parenthesized expressions + Expression expr = returnStatement.Expression; + while (true) { + CastExpression ce = expr as CastExpression; + if (ce != null) { + expr = ce.Expression; + continue; + } + ParenthesizedExpression pe = expr as ParenthesizedExpression; + if (pe != null) { + expr = pe.Expression; + continue; + } + break; + } + // Resolve the expression. - MemberResolveResult mrr = this.Resolve(returnStatement.Expression, this.memberToFind) as MemberResolveResult; + MemberResolveResult mrr = this.Resolve(expr, this.memberToFind.DeclaringType.CompilationUnit.FileName) as MemberResolveResult; if (mrr != null && mrr.ResolvedMember is IField) { PropertyDeclaration pd; @@ -124,7 +140,7 @@ namespace Hornung.ResourceToolkit.Resolver if (this.memberToFind.CompareTo(mrr.ResolvedMember) == 0) { // Resolve the property. - MemberResolveResult prr = ParserService.Resolve(new ExpressionResult(pd.Name, ExpressionContext.Default), pd.StartLocation.Y-1, pd.StartLocation.X, this.memberToFind.DeclaringType.CompilationUnit.FileName, ParserService.GetParseableFileContent(this.memberToFind.DeclaringType.CompilationUnit.FileName)) as MemberResolveResult; + MemberResolveResult prr = NRefactoryAstCacheService.ResolveLowLevel(this.memberToFind.DeclaringType.CompilationUnit.FileName, pd.StartLocation.Y, pd.StartLocation.X+1, null, pd.Name, ExpressionContext.Default) as MemberResolveResult; if (prr != null) { #if DEBUG @@ -160,7 +176,7 @@ namespace Hornung.ResourceToolkit.Resolver assignmentExpression.Op == AssignmentOperatorType.Assign && data != null) { // Resolve the expression. - MemberResolveResult mrr = this.Resolve(assignmentExpression.Left, this.memberToFind) as MemberResolveResult; + MemberResolveResult mrr = this.Resolve(assignmentExpression.Left, this.memberToFind.DeclaringType.CompilationUnit.FileName) as MemberResolveResult; if (mrr != null && mrr.ResolvedMember is IField && !((IField)mrr.ResolvedMember).IsLocalVariable) { PropertyDeclaration pd; @@ -183,7 +199,7 @@ namespace Hornung.ResourceToolkit.Resolver if (this.memberToFind.CompareTo(mrr.ResolvedMember) == 0) { // Resolve the property. - MemberResolveResult prr = ParserService.Resolve(new ExpressionResult(pd.Name, ExpressionContext.Default), pd.StartLocation.Y-1, pd.StartLocation.X, this.memberToFind.DeclaringType.CompilationUnit.FileName, ParserService.GetParseableFileContent(this.memberToFind.DeclaringType.CompilationUnit.FileName)) as MemberResolveResult; + MemberResolveResult prr = NRefactoryAstCacheService.ResolveLowLevel(this.memberToFind.DeclaringType.CompilationUnit.FileName, pd.StartLocation.Y, pd.StartLocation.X+1, null, pd.Name, ExpressionContext.Default) as MemberResolveResult; if (prr != null) { #if DEBUG