Browse Source

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
shortcuts
Christian Hornung 20 years ago
parent
commit
a4574b29cb
  1. 2
      src/AddIns/Misc/ResourceToolkit/Project/ResourceToolkit.csproj
  2. 241
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs
  3. 2
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreNRefactoryResourceResolver.cs
  4. 77
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs
  5. 2
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/INRefactoryResourceResolver.cs
  6. 51
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberEqualityComparer.cs
  7. 207
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberFindAstVisitor.cs
  8. 136
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs
  9. 33
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs
  10. 41
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PositionTrackingAstVisitor.cs
  11. 24
      src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/PropertyFieldAssociationVisitor.cs

2
src/AddIns/Misc/ResourceToolkit/Project/ResourceToolkit.csproj

@ -91,6 +91,8 @@
<Compile Include="Src\Gui\IFilterHost.cs" /> <Compile Include="Src\Gui\IFilterHost.cs" />
<Compile Include="Src\ResourceItem.cs" /> <Compile Include="Src\ResourceItem.cs" />
<EmbeddedResource Include="Resources\EditStringResourceDialog.xfrm" /> <EmbeddedResource Include="Resources\EditStringResourceDialog.xfrm" />
<Compile Include="Src\Resolver\MemberFindAstVisitor.cs" />
<Compile Include="Src\Resolver\MemberEqualityComparer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\..\Main\ICSharpCode.SharpDevelop.Dom\Project\ICSharpCode.SharpDevelop.Dom.csproj"> <ProjectReference Include="..\..\..\..\Main\ICSharpCode.SharpDevelop.Dom\Project\ICSharpCode.SharpDevelop.Dom.csproj">

241
src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/BclNRefactoryResourceResolver.cs

@ -15,6 +15,7 @@ using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Parser; using ICSharpCode.NRefactory.Parser;
using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Project;
using Hornung.ResourceToolkit.ResourceFileContent; using Hornung.ResourceToolkit.ResourceFileContent;
@ -32,7 +33,7 @@ namespace Hornung.ResourceToolkit.Resolver
/// Tries to find a resource reference in the specified expression. /// Tries to find a resource reference in the specified expression.
/// </summary> /// </summary>
/// <param name="expressionResult">The ExpressionResult for the expression.</param> /// <param name="expressionResult">The ExpressionResult for the expression.</param>
/// <param name="expr">The AST representation of the expression.</param> /// <param name="expr">The AST representation of the full expression.</param>
/// <param name="resolveResult">SharpDevelop's ResolveResult for the expression.</param> /// <param name="resolveResult">SharpDevelop's ResolveResult for the expression.</param>
/// <param name="caretLine">The line where the expression is located.</param> /// <param name="caretLine">The line where the expression is located.</param>
/// <param name="caretColumn">The column where the expression is located.</param> /// <param name="caretColumn">The column where the expression is located.</param>
@ -46,13 +47,15 @@ namespace Hornung.ResourceToolkit.Resolver
MemberResolveResult mrr = resolveResult as MemberResolveResult; MemberResolveResult mrr = resolveResult as MemberResolveResult;
if (mrr != null) { if (mrr != null) {
rfc = ResolveResourceFileContent(mrr.ResolvedMember); rfc = ResolveResourceFileContent(mrr.ResolvedMember);
} } else {
LocalResolveResult lrr = resolveResult as LocalResolveResult; LocalResolveResult lrr = resolveResult as LocalResolveResult;
if (lrr != null) { if (lrr != null) {
if (!lrr.IsParameter) { if (!lrr.IsParameter) {
rfc = ResolveResourceFileContent(lrr.Field); rfc = ResolveResourceFileContent(lrr.Field);
}
} }
} }
@ -68,6 +71,26 @@ namespace Hornung.ResourceToolkit.Resolver
// ******************************************************************************************************************************** // ********************************************************************************************************************************
#region ResourceFileContent mapping cache
static Dictionary<IMember, IResourceFileContent> cachedResourceFileContentMappings;
static BclNRefactoryResourceResolver()
{
cachedResourceFileContentMappings = new Dictionary<IMember, IResourceFileContent>(new MemberEqualityComparer());
NRefactoryAstCacheService.CacheEnabledChanged += NRefactoryCacheEnabledChanged;
}
static void NRefactoryCacheEnabledChanged(object sender, EventArgs e)
{
if (!NRefactoryAstCacheService.CacheEnabled) {
// Clear cache when disabled.
cachedResourceFileContentMappings.Clear();
}
}
#endregion
/// <summary> /// <summary>
/// Tries to determine the resource file content which is referenced by the /// Tries to determine the resource file content which is referenced by the
/// resource manager which is assigned to the specified member. /// resource manager which is assigned to the specified member.
@ -79,33 +102,49 @@ namespace Hornung.ResourceToolkit.Resolver
/// </returns> /// </returns>
static IResourceFileContent ResolveResourceFileContent(IMember member) static IResourceFileContent ResolveResourceFileContent(IMember member)
{ {
if (member != null && member.ReturnType != null) { if (member != null && member.ReturnType != null &&
if (IsResourceManager(member.ReturnType) && member.DeclaringType != null && member.DeclaringType.CompilationUnit != null) { member.DeclaringType != null && member.DeclaringType.CompilationUnit != null) {
IResourceFileContent content;
if (!NRefactoryAstCacheService.CacheEnabled || !cachedResourceFileContentMappings.TryGetValue(member, out content)) {
string declaringFileName = member.DeclaringType.CompilationUnit.FileName; string declaringFileName = member.DeclaringType.CompilationUnit.FileName;
if (declaringFileName != null) { if (declaringFileName != null) {
if (IsResourceManager(member.ReturnType, declaringFileName)) {
SupportedLanguage? language = NRefactoryResourceResolver.GetFileLanguage(declaringFileName);
if (language == null) { SupportedLanguage? language = NRefactoryResourceResolver.GetFileLanguage(declaringFileName);
return null; if (language == null) {
} return null;
}
CompilationUnit cu = NRefactoryAstCacheService.GetFullAst(language.Value, declaringFileName);
if (cu != null) {
ResourceManagerInitializationFindVisitor visitor = new ResourceManagerInitializationFindVisitor(member); CompilationUnit cu = NRefactoryAstCacheService.GetFullAst(language.Value, declaringFileName);
cu.AcceptVisitor(visitor, null); if (cu != null) {
if (visitor.FoundFileName != 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; return null;
} }
@ -114,9 +153,21 @@ namespace Hornung.ResourceToolkit.Resolver
/// Determines if the specified type is a ResourceManager type that can /// Determines if the specified type is a ResourceManager type that can
/// be handled by this resolver. /// be handled by this resolver.
/// </summary> /// </summary>
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) { if (resourceManager == null) {
return false; return false;
} }
@ -144,6 +195,9 @@ namespace Hornung.ResourceToolkit.Resolver
readonly IMember resourceManagerMember; readonly IMember resourceManagerMember;
readonly bool isLocalVariable; readonly bool isLocalVariable;
bool triedToResolvePropertyAssociation;
IField resourceManagerFieldAccessedByProperty;
CompilationUnit compilationUnit; CompilationUnit compilationUnit;
string foundFileName; string foundFileName;
@ -188,6 +242,12 @@ namespace Hornung.ResourceToolkit.Resolver
public override object TrackedVisit(VariableDeclaration variableDeclaration, object data) 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; LocalVariableDeclaration localVariableDeclaration = data as LocalVariableDeclaration;
if (this.isLocalVariable && localVariableDeclaration != null) { if (this.isLocalVariable && localVariableDeclaration != null) {
if (variableDeclaration.Name == this.resourceManagerMember.Name) { if (variableDeclaration.Name == this.resourceManagerMember.Name) {
@ -202,20 +262,42 @@ namespace Hornung.ResourceToolkit.Resolver
} }
} }
FieldDeclaration fieldDeclaration = data as FieldDeclaration; FieldDeclaration fieldDeclaration = data as FieldDeclaration;
if (!this.isLocalVariable && fieldDeclaration != null) { if (!this.isLocalVariable && fieldDeclaration != null) {
if (variableDeclaration.Name == this.resourceManagerMember.Name) { // Make sure we got the right declaration by comparing the positions.
// Make sure we got the right declaration by comparing the positions. // Both must have the same start position.
// Both must have the same start position. if (variableDeclaration.Name == this.resourceManagerMember.Name &&
if (fieldDeclaration.StartLocation.X == this.resourceManagerMember.Region.BeginColumn && fieldDeclaration.StartLocation.Y == this.resourceManagerMember.Region.BeginLine) { 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 #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 #endif
data = true; data = true;
} }
} }
} }
return base.TrackedVisit(variableDeclaration, data); 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 (!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; IMember resolvedMember = null;
if (mrr != 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 #if DEBUG
LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver: Resolved member: "+mrr.ResolvedMember.ToString()); LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver: Resolved member: "+resolvedMember.ToString());
#endif #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. // 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 #if DEBUG
LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found assignment to field: "+assignmentExpression.ToString()); 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 // there is a possible relationship between the return types
// of the resolved member and the member we are looking for. // of the resolved member and the member we are looking for.
} else if (this.compilationUnit != null && !this.isLocalVariable && } else if (this.compilationUnit != null && !this.isLocalVariable &&
( IsTypeRelationshipPossible(resolvedMember, this.resourceManagerMember)) {
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())
)
)
)) {
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. // 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); // We might already have found this association in the
this.compilationUnit.AcceptVisitor(visitor, null); // resourceManagerFieldAccessedByProperty field.
if (visitor.AssociatedField != null && visitor.AssociatedField.CompareTo(mrr.ResolvedMember) == 0) { this.TryResolveResourceManagerProperty();
if (this.resourceManagerFieldAccessedByProperty != null && this.resourceManagerFieldAccessedByProperty.CompareTo(resolvedMember) == 0) {
#if DEBUG #if DEBUG
LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found assignment to field: "+assignmentExpression.ToString()); LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found assignment to field: "+assignmentExpression.ToString());
#endif #endif
data = true; 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. // 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); PropertyFieldAssociationVisitor visitor = new PropertyFieldAssociationVisitor((IField)this.resourceManagerMember);
this.compilationUnit.AcceptVisitor(visitor, null); 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 #if DEBUG
LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found assignment to property: "+assignmentExpression.ToString()); LoggingService.Debug("ResourceToolkit: BclNRefactoryResourceResolver found assignment to property: "+assignmentExpression.ToString());
#endif #endif
@ -290,6 +379,35 @@ namespace Hornung.ResourceToolkit.Resolver
return base.TrackedVisit(assignmentExpression, data); return base.TrackedVisit(assignmentExpression, data);
} }
/// <summary>
/// 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.
/// </summary>
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) public override object TrackedVisit(ObjectCreateExpression objectCreateExpression, object data)
{ {
if (data as bool? ?? false) { if (data as bool? ?? false) {
@ -300,7 +418,7 @@ namespace Hornung.ResourceToolkit.Resolver
// Resolve the constructor. // Resolve the constructor.
// A type derived from the declaration type is also allowed. // 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 DEBUG
if (mrr != null) { if (mrr != null) {
@ -345,7 +463,7 @@ namespace Hornung.ResourceToolkit.Resolver
// Support typeof(...) // Support typeof(...)
TypeOfExpression t = param as TypeOfExpression; TypeOfExpression t = param as TypeOfExpression;
if (t != null && this.PositionAvailable) { 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 (trr != null) {
#if DEBUG #if DEBUG
@ -377,6 +495,27 @@ namespace Hornung.ResourceToolkit.Resolver
#endregion #endregion
/// <summary>
/// Determines whether there is a possible relationship between the
/// return types of member1 and member2.
/// </summary>
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);
}
// ******************************************************************************************************************************** // ********************************************************************************************************************************
/// <summary> /// <summary>

2
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. /// Tries to find a resource reference in the specified expression.
/// </summary> /// </summary>
/// <param name="expressionResult">The ExpressionResult for the expression.</param> /// <param name="expressionResult">The ExpressionResult for the expression.</param>
/// <param name="expr">The AST representation of the expression.</param> /// <param name="expr">The AST representation of the full expression.</param>
/// <param name="resolveResult">SharpDevelop's ResolveResult for the expression.</param> /// <param name="resolveResult">SharpDevelop's ResolveResult for the expression.</param>
/// <param name="caretLine">The line where the expression is located.</param> /// <param name="caretLine">The line where the expression is located.</param>
/// <param name="caretColumn">The column where the expression is located.</param> /// <param name="caretColumn">The column where the expression is located.</param>

77
src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/ICSharpCodeCoreResourceResolver.cs

@ -207,13 +207,20 @@ namespace Hornung.ResourceToolkit.Resolver
return null; return null;
} }
string localFile; string localFile = null;
foreach (string relativePath in AddInTree.BuildItems<string>("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/LocalResourcesLocations", null, false)) {
if ((localFile = FindICSharpCodeCoreResourceFile(Path.GetFullPath(Path.Combine(project.Directory, relativePath)))) != null) { if (!NRefactoryAstCacheService.CacheEnabled || !cachedLocalResourceFiles.TryGetValue(project, out localFile)) {
return localFile; foreach (string relativePath in AddInTree.BuildItems<string>("/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;
} }
/// <summary> /// <summary>
@ -223,37 +230,45 @@ namespace Hornung.ResourceToolkit.Resolver
public static string GetICSharpCodeCoreHostResourceFileName(string sourceFileName) public static string GetICSharpCodeCoreHostResourceFileName(string sourceFileName)
{ {
IProject project = ProjectFileDictionaryService.GetProjectForFile(sourceFileName); IProject project = ProjectFileDictionaryService.GetProjectForFile(sourceFileName);
string hostFile = null;
// Get SD directory using the reference to ICSharpCode.Core if (project == null ||
string coreAssemblyFullPath = GetICSharpCodeCoreFullPath(project); !NRefactoryAstCacheService.CacheEnabled || !cachedHostResourceFiles.TryGetValue(project, out hostFile)) {
if (coreAssemblyFullPath == null) { // Get SD directory using the reference to ICSharpCode.Core
// Look for the ICSharpCode.Core project using all available projects. string coreAssemblyFullPath = GetICSharpCodeCoreFullPath(project);
if (ProjectService.OpenSolution != null) {
foreach (IProject p in ProjectService.OpenSolution.Projects) { if (coreAssemblyFullPath == null) {
if ((coreAssemblyFullPath = GetICSharpCodeCoreFullPath(p)) != null) { // Look for the ICSharpCode.Core project using all available projects.
break; 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 #if DEBUG
LoggingService.Debug("ResourceToolkit: ICSharpCodeCoreResourceResolver coreAssemblyFullPath = "+coreAssemblyFullPath); LoggingService.Debug("ResourceToolkit: ICSharpCodeCoreResourceResolver coreAssemblyFullPath = "+coreAssemblyFullPath);
#endif #endif
string hostFile;
foreach (string relativePath in AddInTree.BuildItems<string>("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/HostResourcesLocations", null, false)) { foreach (string relativePath in AddInTree.BuildItems<string>("/AddIns/ResourceToolkit/ICSharpCodeCoreResourceResolver/HostResourcesLocations", null, false)) {
if ((hostFile = FindICSharpCodeCoreResourceFile(Path.GetFullPath(Path.Combine(Path.GetDirectoryName(coreAssemblyFullPath), relativePath)))) != null) { 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) static string GetICSharpCodeCoreFullPath(IProject sourceProject)
@ -296,5 +311,27 @@ namespace Hornung.ResourceToolkit.Resolver
return coreAssemblyFullPath; return coreAssemblyFullPath;
} }
#region ICSharpCode.Core resource file mapping cache
static Dictionary<IProject, string> cachedLocalResourceFiles;
static Dictionary<IProject, string> cachedHostResourceFiles;
static ICSharpCodeCoreResourceResolver()
{
cachedLocalResourceFiles = new Dictionary<IProject, string>();
cachedHostResourceFiles = new Dictionary<IProject, string>();
NRefactoryAstCacheService.CacheEnabledChanged += NRefactoryCacheEnabledChanged;
}
static void NRefactoryCacheEnabledChanged(object sender, EventArgs e)
{
if (!NRefactoryAstCacheService.CacheEnabled) {
// Clear cache when disabled.
cachedLocalResourceFiles.Clear();
cachedHostResourceFiles.Clear();
}
}
#endregion
} }
} }

2
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. /// Tries to find a resource reference in the specified expression.
/// </summary> /// </summary>
/// <param name="expressionResult">The ExpressionResult for the expression.</param> /// <param name="expressionResult">The ExpressionResult for the expression.</param>
/// <param name="expr">The AST representation of the expression.</param> /// <param name="expr">The AST representation of the full expression.</param>
/// <param name="resolveResult">SharpDevelop's ResolveResult for the expression.</param> /// <param name="resolveResult">SharpDevelop's ResolveResult for the expression.</param>
/// <param name="caretLine">The line where the expression is located.</param> /// <param name="caretLine">The line where the expression is located.</param>
/// <param name="caretColumn">The column where the expression is located.</param> /// <param name="caretColumn">The column where the expression is located.</param>

51
src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberEqualityComparer.cs

@ -0,0 +1,51 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Christian Hornung" email="c-hornung@gmx.de"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using ICSharpCode.SharpDevelop.Dom;
namespace Hornung.ResourceToolkit.Resolver
{
/// <summary>
/// Determines equality of DOM members by region and fully qualified name.
/// </summary>
public class MemberEqualityComparer : IEqualityComparer<IMember>
{
public bool Equals(IMember x, IMember y)
{
if (x == null || y == null) {
return false;
}
if (x.Region.CompareTo(y.Region) != 0) {
return false;
}
IComparer<string> 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();
}
}
}

207
src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/MemberFindAstVisitor.cs

@ -0,0 +1,207 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Christian Hornung" email="c-hornung@gmx.de"/>
// <version>$Revision$</version>
// </file>
using System;
using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Visitors;
using ICSharpCode.SharpDevelop.Dom;
namespace Hornung.ResourceToolkit.Resolver
{
/// <summary>
/// Finds a certain member inside the AST.
/// </summary>
public class MemberFindAstVisitor : AbstractAstVisitor
{
readonly IMember memberToFind;
INode memberNode;
/// <summary>
/// Initializes a new instance of the <see cref="MemberFindAstVisitor"/> class.
/// </summary>
/// <param name="member">The member to find.</param>
[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;
}
/// <summary>
/// Gets the INode that belongs to the member specified in the constructor call,
/// or a null reference, if the member cannot be found.
/// </summary>
public INode MemberNode {
get {
return this.memberNode;
}
}
// ********************************************************************************************************************************
/// <summary>
/// Tests whether the specified node is the node belonging to the member we are
/// looking for.
/// </summary>
/// <returns><c>true</c>, if this is the right node or the node has already been found before, otherwise <c>false</c>.</returns>
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);
}
}
}

136
src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryAstCacheService.cs

@ -14,16 +14,23 @@ using ICSharpCode.NRefactory;
using ICSharpCode.NRefactory.Ast; using ICSharpCode.NRefactory.Ast;
using ICSharpCode.NRefactory.Parser; using ICSharpCode.NRefactory.Parser;
using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Dom.NRefactoryResolver;
using ICSharpCode.SharpDevelop.Project;
namespace Hornung.ResourceToolkit.Resolver namespace Hornung.ResourceToolkit.Resolver
{ {
/// <summary> /// <summary>
/// Parses files using NRefactory and caches the AST on demand. /// Parses files using NRefactory and caches the AST on demand.
/// Provides an event to monitor the cache status and
/// the <see cref="ResolveLowLevel"/> method to resolve expressions
/// faster by making use of the cache.
/// </summary> /// </summary>
public static class NRefactoryAstCacheService public static class NRefactoryAstCacheService
{ {
static bool cacheEnabled; static bool cacheEnabled;
static Dictionary<string, CompilationUnit> cachedAstInfo = new Dictionary<string, CompilationUnit>(); static Dictionary<string, CompilationUnit> cachedAstInfo = new Dictionary<string, CompilationUnit>();
static Dictionary<IMember, INode> cachedMemberMappings = new Dictionary<IMember, INode>(new MemberEqualityComparer());
/// <summary> /// <summary>
/// Gets a flag that indicates whether the AST cache is currently enabled. /// Gets a flag that indicates whether the AST cache is currently enabled.
@ -34,6 +41,21 @@ namespace Hornung.ResourceToolkit.Resolver
} }
} }
/// <summary>
/// Occurs when the cache is enabled or disabled.
/// </summary>
public static event EventHandler CacheEnabledChanged;
/// <summary>
/// Raises the CacheEnabledChanged event.
/// </summary>
private static void OnCacheEnabledChanged(EventArgs e)
{
if (CacheEnabledChanged != null) {
CacheEnabledChanged(null, e);
}
}
/// <summary> /// <summary>
/// Enables the AST cache. /// Enables the AST cache.
/// </summary> /// </summary>
@ -46,6 +68,7 @@ namespace Hornung.ResourceToolkit.Resolver
} }
cacheEnabled = true; cacheEnabled = true;
LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService cache enabled"); LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService cache enabled");
OnCacheEnabledChanged(EventArgs.Empty);
} }
/// <summary> /// <summary>
@ -55,7 +78,9 @@ namespace Hornung.ResourceToolkit.Resolver
{ {
cacheEnabled = false; cacheEnabled = false;
cachedAstInfo.Clear(); cachedAstInfo.Clear();
cachedMemberMappings.Clear();
LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService cache disabled and cleared"); LoggingService.Info("ResourceToolkit: NRefactoryAstCacheService cache disabled and cleared");
OnCacheEnabledChanged(EventArgs.Empty);
} }
/// <summary> /// <summary>
@ -91,5 +116,116 @@ namespace Hornung.ResourceToolkit.Resolver
} }
return null; return null;
} }
// ********************************************************************************************************************************
/// <summary>
/// Resolves an expression using low-level NRefactoryResolver methods and making
/// use of the cache if possible.
/// </summary>
/// <param name="fileName">The file name of the source code file that contains the expression to be resolved.</param>
/// <param name="caretLine">The 1-based line number of the expression.</param>
/// <param name="caretColumn">The 1-based column number of the expression.</param>
/// <param name="compilationUnit">The CompilationUnit that contains the NRefactory AST for this file. May be <c>null</c> (then the CompilationUnit is retrieved from the cache or the file is parsed).</param>
/// <param name="expression">The expression to be resolved.</param>
/// <param name="context">The ExpressionContext of the expression.</param>
/// <returns>A ResolveResult or <c>null</c> if the expression cannot be resolved.</returns>
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);
}
}
/// <summary>
/// Resolves an expression using low-level NRefactoryResolver methods and making
/// use of the cache if possible.
/// </summary>
/// <param name="fileName">The file name of the source code file that contains the expression to be resolved.</param>
/// <param name="caretLine">The 1-based line number of the expression.</param>
/// <param name="caretColumn">The 1-based column number of the expression.</param>
/// <param name="compilationUnit">The CompilationUnit that contains the NRefactory AST for this file. May be <c>null</c> (then the CompilationUnit is retrieved from the cache or the file is parsed).</param>
/// <param name="expressionString">The expression to be resolved as a string. If this parameter is <c>null</c>, the expression string is generated by using the code generator.</param>
/// <param name="expression">The parsed expression to be resolved.</param>
/// <param name="context">The ExpressionContext of the expression.</param>
/// <returns>A ResolveResult or <c>null</c> if the expression cannot be resolved.</returns>
[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);
}
} }
} }

33
src/AddIns/Misc/ResourceToolkit/Project/Src/Resolver/NRefactoryResourceResolver.cs

@ -128,23 +128,30 @@ namespace Hornung.ResourceToolkit.Resolver
ResourceResolveResult rrr = null; 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); SupportedLanguage? language = GetFileLanguage(fileName);
if (language != null) { if (language == null) {
using(ICSharpCode.NRefactory.IParser parser = ParserFactory.CreateParser(language.Value, new StringReader(result.Expression))) { return null;
if (parser != null) {
expr = parser.ParseExpression();
}
}
} }
// The resolve routine needs the member which contains the actual member being referenced. // 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 // If a complete expression is given, the expression needs to be reduced to
// the member reference. // the member reference.
Expression fullExpr = null;
while (result.Expression != null && result.Expression.Length > 0) { 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; break;
} }
result.Expression = ef.RemoveLastPart(result.Expression); result.Expression = ef.RemoveLastPart(result.Expression);
@ -163,14 +170,14 @@ namespace Hornung.ResourceToolkit.Resolver
/// Tries to resolve the resource reference using all available /// Tries to resolve the resource reference using all available
/// NRefactory resource resolvers. /// NRefactory resource resolvers.
/// </summary> /// </summary>
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) { if (rr != null) {
ResourceResolveResult rrr; ResourceResolveResult rrr;
foreach (INRefactoryResourceResolver resolver in Resolvers) { 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; return rrr;
} }
} }

41
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);
}
// ********************************************************************************************************************************
/// <summary> /// <summary>
/// Resolves an expression in the current node's context. /// Resolves an expression in the current node's context.
/// </summary> /// </summary>
/// <param name="expression">The expression to be resolved.</param> /// <param name="expression">The expression to be resolved.</param>
/// <param name="memberInThisFile">Any member declared in the source file in question. Used to get the language, file name and file content.</param> /// <param name="fileName">The file name of the source file that contains the expression to be resolved.</param>
public ResolveResult Resolve(Expression expression, IMember memberInThisFile) public ResolveResult Resolve(Expression expression, string fileName)
{ {
if (!this.PositionAvailable) { 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; return null;
} }
// In order to resolve expression, we need the original code. #if DEBUG
// HACK: To get the code belonging to this expression, we pass it through the code generator. LoggingService.Debug("ResourceToolkit: PositionTrackingAstVisitor: Using this parent node for resolve: "+this.parentNodes.Peek().ToString());
// (Is there a better way?) #endif
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());
}
return null; return NRefactoryAstCacheService.ResolveLowLevel(fileName, this.CurrentNodeStartLocation.Y, this.CurrentNodeStartLocation.X+1, this.compilationUnit, null, expression, ExpressionContext.Default);
} }
// ******************************************************************************************************************************** // ********************************************************************************************************************************

24
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 if (this.associatedMember == null && // skip if already found to improve performance
this.currentContext == VisitorContext.PropertyGetRegion && data != null) { 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. // 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) { if (mrr != null && mrr.ResolvedMember is IField) {
PropertyDeclaration pd; PropertyDeclaration pd;
@ -124,7 +140,7 @@ namespace Hornung.ResourceToolkit.Resolver
if (this.memberToFind.CompareTo(mrr.ResolvedMember) == 0) { if (this.memberToFind.CompareTo(mrr.ResolvedMember) == 0) {
// Resolve the property. // 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 (prr != null) {
#if DEBUG #if DEBUG
@ -160,7 +176,7 @@ namespace Hornung.ResourceToolkit.Resolver
assignmentExpression.Op == AssignmentOperatorType.Assign && data != null) { assignmentExpression.Op == AssignmentOperatorType.Assign && data != null) {
// Resolve the expression. // 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) { if (mrr != null && mrr.ResolvedMember is IField && !((IField)mrr.ResolvedMember).IsLocalVariable) {
PropertyDeclaration pd; PropertyDeclaration pd;
@ -183,7 +199,7 @@ namespace Hornung.ResourceToolkit.Resolver
if (this.memberToFind.CompareTo(mrr.ResolvedMember) == 0) { if (this.memberToFind.CompareTo(mrr.ResolvedMember) == 0) {
// Resolve the property. // 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 (prr != null) {
#if DEBUG #if DEBUG

Loading…
Cancel
Save