diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolveAtLocationTests.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolveAtLocationTests.cs new file mode 100644 index 0000000000..740bffb80e --- /dev/null +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolveAtLocationTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using ICSharpCode.NRefactory.TypeSystem; +using NUnit.Framework; + +namespace ICSharpCode.NRefactory.CSharp.Resolver +{ + [TestFixture] + public class ResolveAtLocationTests : ResolverTestBase + { + [Test] + public void UsingDeclaration() + { + Assert.IsNull(ResolveAtLocation("usi$ng System;")); + } + + [Test] + public void UsingDeclarationNamespace() + { + var rr = ResolveAtLocation("using $System;"); + Assert.AreEqual("System", rr.NamespaceName); + } + + [Test] + public void CatchClauseVariable() + { + var rr = ResolveAtLocation("using System; public class A { void M() { try { } catch (Exception e$x) { } } }"); + Assert.AreEqual("ex", rr.Variable.Name); + Assert.AreEqual("System.Exception", rr.Type.FullName); + } + + [Test] + public void MethodInvocation() + { + var rr = ResolveAtLocation(@"using System; +class A { void M() { + Console.W$riteLine(1); +}}"); + Assert.AreEqual("System.Console.WriteLine", rr.Member.FullName); + Assert.AreEqual("System.Int32", rr.Member.Parameters[0].Type.Resolve(context).FullName); + } + + [Test] + public void ImplicitlyTypedVariable() + { + var rr = ResolveAtLocation(@"using System; +class A { void M() { + v$ar x = Environment.TickCount; +}}"); + Assert.AreEqual("System.Int32", rr.Type.FullName); + } + + [Test, Ignore("Parser returns incorrect positions")] + public void BaseCtorCall() + { + var rr = ResolveAtLocation(@"using System; +class A { public A() : ba$se() {} }"); + Assert.AreEqual("System.Object..ctor", rr.Member.FullName); + } + } +} diff --git a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs index a264c0aa46..2d9f875e6e 100644 --- a/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs +++ b/ICSharpCode.NRefactory.Tests/CSharp/Resolver/ResolverTestBase.cs @@ -254,5 +254,31 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver } } } + + protected ResolveResult ResolveAtLocation(string code) + { + CompilationUnit cu = new CSharpParser().Parse(new StringReader(code.Replace("$", ""))); + + AstLocation[] dollars = FindDollarSigns(code).ToArray(); + Assert.AreEqual(1, dollars.Length, "Expected 1 dollar signs marking the location"); + + SetUp(); + + ParsedFile parsedFile = new ParsedFile("test.cs", resolver.CurrentUsingScope); + TypeSystemConvertVisitor convertVisitor = new TypeSystemConvertVisitor(parsedFile, resolver.CurrentUsingScope, null); + cu.AcceptVisitor(convertVisitor, null); + project.UpdateProjectContent(null, convertVisitor.ParsedFile); + + ResolveResult rr = Resolver.ResolveAtLocation.Resolve(this.context, parsedFile, cu, dollars[0]); + return rr; + } + + protected T ResolveAtLocation(string code) where T : ResolveResult + { + ResolveResult rr = ResolveAtLocation(code); + Assert.IsNotNull(rr); + Assert.IsTrue(rr.GetType() == typeof(T), "Resolve should be " + typeof(T).Name + ", but was " + rr.GetType().Name); + return (T)rr; + } } } diff --git a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj index 796729ebda..5d951a4b2a 100644 --- a/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj +++ b/ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj @@ -155,6 +155,7 @@ + diff --git a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs index 3c089a63c0..943ebba115 100644 --- a/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs +++ b/ICSharpCode.NRefactory/CSharp/Ast/AstNode.cs @@ -562,62 +562,6 @@ namespace ICSharpCode.NRefactory.CSharp return result; } - public AstNode GetResolveableNodeAt (int line, int column) - { - return GetResolveableNodeAt (new AstLocation (line, column)); - } - - /// - /// Gets a node that can be resolved at location. - /// - public AstNode GetResolveableNodeAt (AstLocation location) - { - return GetNodeAt (location, delegate (AstNode n) { - - if (n is TypeDeclaration) { - var decl = (TypeDeclaration)n; - return decl.NameToken.StartLocation <= location && location <= decl.NameToken.EndLocation; - } - - if (n is DelegateDeclaration) { - var decl = (DelegateDeclaration)n; - return decl.NameToken.StartLocation <= location && location <= decl.NameToken.EndLocation; - } - - if (n is MemberDeclaration) { - var decl = (MemberDeclaration)n; - return decl.NameToken.StartLocation <= location && location <= decl.NameToken.EndLocation; - } - - if (n is ConstructorDeclaration) { - var decl = (ConstructorDeclaration)n; - return decl.IdentifierToken.StartLocation <= location && location <= decl.IdentifierToken.EndLocation; - } - - if (n is DestructorDeclaration) { - var decl = (DestructorDeclaration)n; - return decl.IdentifierToken.StartLocation <= location && location <= decl.IdentifierToken.EndLocation; - } - - if (n is VariableInitializer) { - var decl = (VariableInitializer)n; - return decl.NameToken.StartLocation <= location && location <= decl.NameToken.EndLocation; - } - - if (n is ParameterDeclaration) { - var decl = (ParameterDeclaration)n; - return decl.NameToken.StartLocation <= location && location <= decl.NameToken.EndLocation; - } - - if (n is MemberReferenceExpression) { - var decl = (MemberReferenceExpression)n; - return decl.MemberNameToken.StartLocation <= location && location <= decl.MemberNameToken.EndLocation; - } - - return n is IdentifierExpression || n is AstType; - }); - } - public IEnumerable GetNodesBetween (int startLine, int startColumn, int endLine, int endColumn) { return GetNodesBetween (new AstLocation (startLine, startColumn), new AstLocation (endLine, endColumn)); diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/ResolveAtLocation.cs b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveAtLocation.cs new file mode 100644 index 0000000000..dcf78a2080 --- /dev/null +++ b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveAtLocation.cs @@ -0,0 +1,76 @@ +// Copyright (c) AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Threading; +using ICSharpCode.NRefactory.TypeSystem; + +namespace ICSharpCode.NRefactory.CSharp.Resolver +{ + /// + /// Helper class that resolves the node at a specified location. + /// Can be used for implementing tool tips. + /// + public static class ResolveAtLocation + { + public static ResolveResult Resolve(ITypeResolveContext context, ParsedFile parsedFile, CompilationUnit cu, AstLocation location, + CancellationToken cancellationToken = default(CancellationToken)) + { + AstNode node = cu.GetNodeAt(location); + AstNode resolvableNode; + if (node is Identifier) { + resolvableNode = node.Parent; + } else if (node.NodeType == NodeType.Token) { + if (node.Parent is ConstructorInitializer) { + resolvableNode = node.Parent; + } else { + return null; + } + } else { + // don't resolve arbitrary nodes - we don't want to show tooltips for everything + return null; + } + + InvocationExpression parentInvocation = null; + if ((resolvableNode is IdentifierExpression || resolvableNode is MemberReferenceExpression || resolvableNode is PointerReferenceExpression)) { + // we also need to resolve the invocation + parentInvocation = resolvableNode.Parent as InvocationExpression; + } + + IResolveVisitorNavigator navigator; + if (parentInvocation != null) + navigator = new NodeListResolveVisitorNavigator(new[] { resolvableNode, parentInvocation }); + else + navigator = new NodeListResolveVisitorNavigator(new[] { resolvableNode }); + + using (var ctx = context.Synchronize()) { + CSharpResolver resolver = new CSharpResolver(ctx, cancellationToken); + ResolveVisitor v = new ResolveVisitor(resolver, parsedFile, navigator); + v.Scan(cu); + + // Prefer the RR from the token itself, if it was assigned a ResolveResult + // (this can happen with the identifiers in various nodes such as catch clauses or foreach statements) + ResolveResult rr = v.GetResolveResult(node) ?? v.GetResolveResult(resolvableNode); + if (rr is MethodGroupResolveResult && parentInvocation != null) + return v.GetResolveResult(parentInvocation); + else + return rr; + } + } + } +} diff --git a/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs index eb54375132..f026784125 100644 --- a/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs +++ b/ICSharpCode.NRefactory/CSharp/Resolver/ResolveVisitor.cs @@ -2076,17 +2076,15 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver public override ResolveResult VisitCatchClause(CatchClause catchClause, object data) { resolver.PushBlock(); - IVariable v = null; if (catchClause.VariableName != null) { - v = resolver.AddVariable(MakeTypeReference(catchClause.Type, null, false), MakeRegion(catchClause.VariableNameToken), catchClause.VariableName); + ITypeReference variableType = MakeTypeReference(catchClause.Type, null, false); + DomRegion region = MakeRegion(catchClause.VariableNameToken); + IVariable v = resolver.AddVariable(variableType, region, catchClause.VariableName); + StoreResult(catchClause.VariableNameToken, new LocalResolveResult(v, v.Type.Resolve(resolver.Context))); } ScanChildren(catchClause); resolver.PopBlock(); - if (resolverEnabled && v != null) { - return new LocalResolveResult(v, v.Type.Resolve(resolver.Context)); - } else { - return null; - } + return voidResult; } #endregion @@ -2420,7 +2418,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver currentTypeLookupMode = SimpleNameLookupMode.TypeInUsingDeclaration; ScanChildren(usingDeclaration); currentTypeLookupMode = SimpleNameLookupMode.Type; - return null; + return voidResult; } public override ResolveResult VisitUsingAliasDeclaration(UsingAliasDeclaration usingDeclaration, object data) @@ -2428,7 +2426,7 @@ namespace ICSharpCode.NRefactory.CSharp.Resolver currentTypeLookupMode = SimpleNameLookupMode.TypeInUsingDeclaration; ScanChildren(usingDeclaration); currentTypeLookupMode = SimpleNameLookupMode.Type; - return null; + return voidResult; } #endregion diff --git a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj index 065f5a1074..3ac91f5a60 100644 --- a/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj +++ b/ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj @@ -110,6 +110,7 @@ +