diff --git a/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs b/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs index b3b020579c..4d91e4e187 100644 --- a/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs +++ b/src/Main/Base/Project/Src/Services/RefactoringService/RefactoringService.cs @@ -6,11 +6,13 @@ // 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 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 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 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 return false; } - /// - /// Determines the token that denotes a possible beginning of an indexer - /// expression in the specified file. - /// - 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 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; diff --git a/src/Main/Base/Test/NRefactoryResolverTests.cs b/src/Main/Base/Test/NRefactoryResolverTests.cs index c4eabdf20c..5b39060c0d 100644 --- a/src/Main/Base/Test/NRefactoryResolverTests.cs +++ b/src/Main/Base/Test/NRefactoryResolverTests.cs @@ -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(program, "new A(2)", 3); + IMember aCtor = mrr.ResolvedMember; + Assert.AreEqual("A.#ctor", mrr.ResolvedMember.FullyQualifiedName); + + mrr = Resolve(program, "new B(2)", 3); + IMember bCtor = mrr.ResolvedMember; + Assert.AreEqual("B.#ctor", mrr.ResolvedMember.FullyQualifiedName); + + mrr = Resolve(program, "base(a)", 7); + Assert.AreEqual("A.#ctor", mrr.ResolvedMember.FullyQualifiedName); + + mrr = Resolve(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) diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/ICSharpCode.SharpDevelop.Dom.csproj b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/ICSharpCode.SharpDevelop.Dom.csproj index fa127d7bbb..305f9f02eb 100644 --- a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/ICSharpCode.SharpDevelop.Dom.csproj +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/ICSharpCode.SharpDevelop.Dom.csproj @@ -96,6 +96,7 @@ + diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/LanguageProperties.cs b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/LanguageProperties.cs index 20643852d4..5f75bdf2ef 100644 --- a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/LanguageProperties.cs +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/LanguageProperties.cs @@ -6,6 +6,8 @@ // 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 } /// - /// 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 '('. /// 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 { 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 } } - 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 } } + 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 } } diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/TypeVisitor.cs b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/TypeVisitor.cs index a4c3bbe1ee..50fa9ce97d 100644 --- a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/TypeVisitor.cs +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/NRefactoryResolver/TypeVisitor.cs @@ -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 return null; } + IList 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)); diff --git a/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/TextFinder.cs b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/TextFinder.cs new file mode 100644 index 0000000000..52f1fbc6bf --- /dev/null +++ b/src/Main/ICSharpCode.SharpDevelop.Dom/Project/Src/Refactoring/TextFinder.cs @@ -0,0 +1,45 @@ +// +// +// +// +// $Revision$ +// + +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); + } +}