Browse Source

Support "go to definition" and "find references" for C# base constructor calls (": base(...)").

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@2508 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 19 years ago
parent
commit
63b9f9061a
  1. 82
      src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs
  2. 38
      src/Main/Base/Test/NRefactoryResolverTests.cs
  3. 1
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/ICSharpCode.SharpDevelop.Dom.csproj
  4. 140
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/LanguageProperties.cs
  5. 16
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/TypeVisitor.cs
  6. 45
      src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/TextFinder.cs

82
src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs

@ -6,11 +6,13 @@ @@ -6,11 +6,13 @@
// </file>
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Drawing;
using ICSharpCode.Core;
using ICSharpCode.SharpDevelop.Dom;
using ICSharpCode.SharpDevelop.Dom.Refactoring;
using ICSharpCode.SharpDevelop.Gui;
using ICSharpCode.SharpDevelop.Project;
@ -177,58 +179,36 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -177,58 +179,36 @@ namespace ICSharpCode.SharpDevelop.Refactoring
bool isLocal,
string fileName, string fileContent)
{
string lowerFileContent = fileContent.ToLowerInvariant();
string searchedText; // the text that is searched for
bool searchingIndexer = false;
TextFinder textFinder; // the class used to find the position to resolve
if (member == null) {
searchedText = parentClass.Name.ToLowerInvariant();
textFinder = parentClass.ProjectContent.Language.GetFindClassReferencesTextFinder(parentClass);
} else {
// When looking for a member, the name of the parent class does not always exist
// in the file where the member is accessed.
// (examples: derived classes, partial classes)
if (member is IMethod && ((IMethod)member).IsConstructor)
searchedText = parentClass.Name.ToLowerInvariant();
else {
if (member is IProperty && ((IProperty)member).IsIndexer) {
searchingIndexer = true;
searchedText = GetIndexerExpressionStartToken(fileName);
} else {
searchedText = member.Name.ToLowerInvariant();
}
}
Debug.Assert(member.DeclaringType == parentClass);
textFinder = parentClass.ProjectContent.Language.GetFindMemberReferencesTextFinder(member);
}
// It is possible that a class or member does not have a name (when parsing incomplete class definitions)
// - in that case, we cannot find references.
if (searchedText.Length == 0) {
if (textFinder == null) {
return;
}
int pos = -1;
int exprPos;
string fileContentForFinder = textFinder.PrepareInputText(fileContent);
IExpressionFinder expressionFinder = null;
while ((pos = lowerFileContent.IndexOf(searchedText, pos + 1)) >= 0) {
if (!searchingIndexer) {
if (pos > 0 && char.IsLetterOrDigit(fileContent, pos - 1)) {
continue; // memberName is not a whole word (a.SomeName cannot reference Name)
}
if (pos < fileContent.Length - searchedText.Length - 1
&& char.IsLetterOrDigit(fileContent, pos + searchedText.Length))
{
continue; // memberName is not a whole word (a.Name2 cannot reference Name)
}
exprPos = pos;
} else {
exprPos = pos-1; // indexer expressions are found by resolving the part before the indexer
}
TextFinderMatch match = new TextFinderMatch(-1, 0);
while (true) {
match = textFinder.Find(fileContentForFinder, match.Position + 1);
if (match.Position < 0)
break;
if (expressionFinder == null) {
expressionFinder = ParserService.GetExpressionFinder(fileName);
}
ExpressionResult expr = expressionFinder.FindFullExpression(fileContent, exprPos);
ExpressionResult expr = expressionFinder.FindFullExpression(fileContent, match.ResolvePosition);
if (expr.Expression != null) {
Point position = GetPosition(fileContent, exprPos);
Point position = GetPosition(fileContent, match.ResolvePosition);
repeatResolve:
// TODO: Optimize by re-using the same resolver if multiple expressions were
// found in this file (the resolver should parse all methods at once)
@ -237,14 +217,14 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -237,14 +217,14 @@ namespace ICSharpCode.SharpDevelop.Refactoring
if (isLocal) {
// find reference to local variable
if (IsReferenceToLocalVariable(rr, member)) {
list.Add(new Reference(fileName, pos, searchedText.Length, expr.Expression, rr));
list.Add(new Reference(fileName, match.Position, match.Length, expr.Expression, rr));
} else if (FixIndexerExpression(expressionFinder, ref expr, mrr)) {
goto repeatResolve;
}
} else if (member != null) {
// find reference to member
if (IsReferenceToMember(member, rr)) {
list.Add(new Reference(fileName, pos, searchedText.Length, expr.Expression, rr));
list.Add(new Reference(fileName, match.Position, match.Length, expr.Expression, rr));
} else if (FixIndexerExpression(expressionFinder, ref expr, mrr)) {
goto repeatResolve;
}
@ -253,12 +233,12 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -253,12 +233,12 @@ namespace ICSharpCode.SharpDevelop.Refactoring
if (mrr != null) {
if (mrr.ResolvedMember is IMethod && ((IMethod)mrr.ResolvedMember).IsConstructor) {
if (mrr.ResolvedMember.DeclaringType.FullyQualifiedName == parentClass.FullyQualifiedName) {
list.Add(new Reference(fileName, pos, searchedText.Length, expr.Expression, rr));
list.Add(new Reference(fileName, match.Position, match.Length, expr.Expression, rr));
}
}
} else {
if (rr is TypeResolveResult && rr.ResolvedType.FullyQualifiedName == parentClass.FullyQualifiedName) {
list.Add(new Reference(fileName, pos, searchedText.Length, expr.Expression, rr));
list.Add(new Reference(fileName, match.Position, match.Length, expr.Expression, rr));
}
}
}
@ -286,25 +266,6 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -286,25 +266,6 @@ namespace ICSharpCode.SharpDevelop.Refactoring
return false;
}
/// <summary>
/// Determines the token that denotes a possible beginning of an indexer
/// expression in the specified file.
/// </summary>
static string GetIndexerExpressionStartToken(string fileName)
{
if (fileName != null) {
ParseInformation pi = ParserService.GetParseInformation(fileName);
if (pi != null &&
pi.MostRecentCompilationUnit != null &&
pi.MostRecentCompilationUnit.ProjectContent != null &&
pi.MostRecentCompilationUnit.ProjectContent.Language != null) {
return pi.MostRecentCompilationUnit.ProjectContent.Language.IndexerExpressionStartToken;
}
}
LoggingService.Warn("RefactoringService: unable to determine the correct indexer expression start token for file '"+fileName+"'");
return LanguageProperties.CSharp.IndexerExpressionStartToken;
}
static Point GetPosition(string fileContent, int pos)
{
int line = 1;
@ -547,6 +508,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring @@ -547,6 +508,7 @@ namespace ICSharpCode.SharpDevelop.Refactoring
public static IMember FindBaseMember(IMember member)
{
if (member == null) return null;
if (member is IMethod && (member as IMethod).IsConstructor) return null;
IClass parentClass = member.DeclaringType;
IClass baseClass = parentClass.BaseClass;
if (baseClass == null) return null;

38
src/Main/Base/Test/NRefactoryResolverTests.cs

@ -143,6 +143,44 @@ class TestClass { @@ -143,6 +143,44 @@ class TestClass {
// to the generic method.
Assert.IsTrue(Refactoring.RefactoringService.IsReferenceToMember(genericMethod, mrr));
}
[Test]
public void ConstructorBaseCall()
{
string program = @"using System;
class A {
public A(int a) {}
}
class B : A {
public B(int a)
: base(a) /*7*/
{}
}
class C : B {
public C(int a)
: base(a) /*12*/
{}
}
";
MemberResolveResult mrr = Resolve<MemberResolveResult>(program, "new A(2)", 3);
IMember aCtor = mrr.ResolvedMember;
Assert.AreEqual("A.#ctor", mrr.ResolvedMember.FullyQualifiedName);
mrr = Resolve<MemberResolveResult>(program, "new B(2)", 3);
IMember bCtor = mrr.ResolvedMember;
Assert.AreEqual("B.#ctor", mrr.ResolvedMember.FullyQualifiedName);
mrr = Resolve<MemberResolveResult>(program, "base(a)", 7);
Assert.AreEqual("A.#ctor", mrr.ResolvedMember.FullyQualifiedName);
mrr = Resolve<MemberResolveResult>(program, "base(a)", 12);
Assert.AreEqual("B.#ctor", mrr.ResolvedMember.FullyQualifiedName);
// ensure that the reference pointing to the B ctor is not seen as a reference
// to the A ctor.
Assert.IsTrue(Refactoring.RefactoringService.IsReferenceToMember(bCtor, mrr));
Assert.IsFalse(Refactoring.RefactoringService.IsReferenceToMember(aCtor, mrr));
}
#endregion
#region Test for old issues (Fidalgo)

1
src/Main/ICSharpCode.SharpDevelop.Dom/Project/ICSharpCode.SharpDevelop.Dom.csproj

@ -96,6 +96,7 @@ @@ -96,6 +96,7 @@
<Compile Include="Src\NRefactoryResolver\VBNetToCSharpConvertVisitor.cs" />
<Compile Include="Src\ProjectContent\DomAssemblyName.cs" />
<Compile Include="Src\Refactoring\CodeGeneratorOptions.cs" />
<Compile Include="Src\Refactoring\TextFinder.cs" />
<Compile Include="Src\ReflectionLayer\DomPersistence.cs" />
<Compile Include="Src\ReflectionLayer\ReflectionClass.cs" />
<Compile Include="Src\ReflectionLayer\ReflectionEvent.cs" />

140
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/LanguageProperties.cs

@ -6,6 +6,8 @@ @@ -6,6 +6,8 @@
// </file>
using System;
using System.Collections.Generic;
using System.Diagnostics;
using ICSharpCode.SharpDevelop.Dom.Refactoring;
namespace ICSharpCode.SharpDevelop.Dom
@ -169,12 +171,27 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -169,12 +171,27 @@ namespace ICSharpCode.SharpDevelop.Dom
}
/// <summary>
/// Gets the token that denotes a possible beginning of an indexer expression.
/// Gets the start token of an indexer expression in the language. Usually '[' or '('.
/// </summary>
public virtual string IndexerExpressionStartToken {
get { return "["; }
}
public virtual TextFinder GetFindClassReferencesTextFinder(IClass c)
{
return new WholeWordTextFinder(c.Name, nameComparer);
}
public virtual TextFinder GetFindMemberReferencesTextFinder(IMember member)
{
IProperty property = member as IProperty;
if (property != null && property.IsIndexer) {
return new IndexBeforeTextFinder(IndexerExpressionStartToken);
} else {
return new WholeWordTextFinder(member.Name, nameComparer);
}
}
public virtual bool IsClassWithImplicitlyStaticMembers(IClass c)
{
return false;
@ -259,6 +276,20 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -259,6 +276,20 @@ namespace ICSharpCode.SharpDevelop.Dom
{
return "[LanguageProperties: C#]";
}
public override TextFinder GetFindMemberReferencesTextFinder(IMember member)
{
IMethod method = member as IMethod;
if (method != null && method.IsConstructor) {
return new CombinedTextFinder(
new WholeWordTextFinder(member.DeclaringType.Name, this.NameComparer),
new WholeWordTextFinder("this", this.NameComparer),
new WholeWordTextFinder("base", this.NameComparer)
);
} else {
return base.GetFindMemberReferencesTextFinder(member);
}
}
}
#endregion
@ -293,12 +324,6 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -293,12 +324,6 @@ namespace ICSharpCode.SharpDevelop.Dom
}
}
public override string IndexerExpressionStartToken {
get {
return "(";
}
}
public override bool IsClassWithImplicitlyStaticMembers(IClass c)
{
return c.ClassType == ClassType.Module;
@ -350,11 +375,112 @@ namespace ICSharpCode.SharpDevelop.Dom @@ -350,11 +375,112 @@ namespace ICSharpCode.SharpDevelop.Dom
}
}
public override string IndexerExpressionStartToken {
get { return "("; }
}
public override string ToString()
{
return "[LanguageProperties: VB.NET]";
}
}
#endregion
#region Text Finder
protected sealed class WholeWordTextFinder : TextFinder
{
readonly string searchedText;
readonly bool caseInsensitive;
public WholeWordTextFinder(string word, StringComparer nameComparer)
{
if (word == null) word = string.Empty;
caseInsensitive = nameComparer.Equals("a", "A");
if (caseInsensitive)
this.searchedText = word.ToLowerInvariant();
else
this.searchedText = word;
}
public override string PrepareInputText(string inputText)
{
if (caseInsensitive)
return inputText.ToLowerInvariant();
else
return inputText;
}
public override TextFinderMatch Find(string inputText, int startPosition)
{
if (searchedText.Length == 0)
return TextFinderMatch.Empty;
int pos = startPosition - 1;
while ((pos = inputText.IndexOf(searchedText, pos + 1)) >= 0) {
if (pos > 0 && char.IsLetterOrDigit(inputText, pos - 1)) {
continue; // memberName is not a whole word (a.SomeName cannot reference Name)
}
if (pos < inputText.Length - searchedText.Length - 1
&& char.IsLetterOrDigit(inputText, pos + searchedText.Length))
{
continue; // memberName is not a whole word (a.Name2 cannot reference Name)
}
return new TextFinderMatch(pos, searchedText.Length);
}
return TextFinderMatch.Empty;
}
}
protected sealed class CombinedTextFinder : TextFinder
{
readonly TextFinder[] finders;
public CombinedTextFinder(params TextFinder[] finders)
{
if (finders == null)
throw new ArgumentNullException("finders");
if (finders.Length == 0)
throw new ArgumentException("finders.Length must be > 0");
this.finders = finders;
}
public override string PrepareInputText(string inputText)
{
return finders[0].PrepareInputText(inputText);
}
public override TextFinderMatch Find(string inputText, int startPosition)
{
TextFinderMatch best = TextFinderMatch.Empty;
foreach (TextFinder f in finders) {
TextFinderMatch r = f.Find(inputText, startPosition);
if (r.Position >= 0 && (best.Position < 0 || r.Position < best.Position)) {
best = r;
}
}
return best;
}
}
protected sealed class IndexBeforeTextFinder : TextFinder
{
readonly string searchText;
public IndexBeforeTextFinder(string searchText)
{
this.searchText = searchText;
}
public override TextFinderMatch Find(string inputText, int startPosition)
{
int pos = inputText.IndexOf(searchText, startPosition);
if (pos >= 0) {
return new TextFinderMatch(pos, searchText.Length, pos - 1);
} else {
return TextFinderMatch.Empty;
}
}
}
#endregion
}
}

16
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/TypeVisitor.cs

@ -202,6 +202,15 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver @@ -202,6 +202,15 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver
return GetVisualBasicIndexer(invocationExpression);
return FindOverload(methods, typeParameters, invocationExpression.Arguments);
} else {
if (resolver.Language == SupportedLanguage.CSharp && resolver.CallingClass != null) {
if (invocationExpression.TargetObject is ThisReferenceExpression) {
// call to constructor
return FindOverload(GetConstructors(resolver.CallingClass), typeParameters, invocationExpression.Arguments);
} else if (invocationExpression.TargetObject is BaseReferenceExpression) {
return FindOverload(GetConstructors(resolver.CallingClass.BaseClass), typeParameters, invocationExpression.Arguments);
}
}
// this could be a nested indexer access
if (resolver.Language == SupportedLanguage.VBNet)
return GetVisualBasicIndexer(invocationExpression);
@ -209,6 +218,13 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver @@ -209,6 +218,13 @@ namespace ICSharpCode.SharpDevelop.Dom.NRefactoryResolver
return null;
}
IList<IMethod> GetConstructors(IClass c)
{
if (c == null)
return emptyMethodsArray;
return c.Methods.FindAll(delegate(IMethod m) { return m.IsConstructor; });
}
IProperty GetVisualBasicIndexer(InvocationExpression invocationExpression)
{
return GetIndexer(new IndexerExpression(invocationExpression.TargetObject, invocationExpression.Arguments));

45
src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/TextFinder.cs

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
namespace ICSharpCode.SharpDevelop.Dom.Refactoring
{
public struct TextFinderMatch
{
public readonly int Position;
public readonly int Length;
public readonly int ResolvePosition;
public static readonly TextFinderMatch Empty = new TextFinderMatch(-1, 0);
public TextFinderMatch(int position, int length)
{
this.Position = position;
this.Length = length;
this.ResolvePosition = position;
}
public TextFinderMatch(int position, int length, int resolvePosition)
{
this.Position = position;
this.Length = length;
this.ResolvePosition = resolvePosition;
}
}
public abstract class TextFinder
{
public virtual string PrepareInputText(string inputText)
{
return inputText;
}
public abstract TextFinderMatch Find(string inputText, int startPosition);
}
}
Loading…
Cancel
Save