Skip to content
This repository was archived by the owner on Nov 4, 2024. It is now read-only.

Commit e7608e4

Browse files
author
Mikhail Arkhipov
authored
Implement goto declaration (microsoft#1343)
* Goto declaration * Some more tests
1 parent ea052e3 commit e7608e4

File tree

8 files changed

+129
-26
lines changed

8 files changed

+129
-26
lines changed

src/Analysis/Ast/Impl/Extensions/MemberExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,5 +86,21 @@ public static string GetName(this IMember m) {
8686
}
8787
return null;
8888
}
89+
90+
public static ILocatedMember GetRootDefinition(this ILocatedMember lm) {
91+
if (!(lm is IImportedMember im) || im.Parent == null) {
92+
return lm;
93+
}
94+
95+
var parent = im.Parent;
96+
for (; parent != null;) {
97+
if (!(parent is IImportedMember im1) || im1.Parent == null) {
98+
break;
99+
}
100+
parent = im1.Parent;
101+
}
102+
return parent;
103+
}
104+
89105
}
90106
}

src/LanguageServer/Impl/Implementation/Server.Editor.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ public async Task<Reference[]> GotoDefinition(TextDocumentPositionParams @params
9292
return reference != null ? new[] { reference } : Array.Empty<Reference>();
9393
}
9494

95+
public async Task<Location> GotoDeclaration(TextDocumentPositionParams @params, CancellationToken cancellationToken) {
96+
var uri = @params.textDocument.uri;
97+
_log?.Log(TraceEventType.Verbose, $"Goto Declaration in {uri} at {@params.position}");
98+
99+
var analysis = await Document.GetAnalysisAsync(uri, Services, CompletionAnalysisTimeout, cancellationToken);
100+
var reference = new DeclarationSource(Services).FindDefinition(analysis, @params.position, out _);
101+
return reference != null ? new Location { uri = reference.uri, range = reference.range} : null;
102+
}
103+
95104
public Task<Reference[]> FindReferences(ReferencesParams @params, CancellationToken cancellationToken) {
96105
var uri = @params.textDocument.uri;
97106
_log?.Log(TraceEventType.Verbose, $"References in {uri} at {@params.position}");

src/LanguageServer/Impl/Implementation/Server.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public Server(IServiceManager services) {
8888
workspaceSymbolProvider = true,
8989
documentSymbolProvider = true,
9090
renameProvider = true,
91+
declarationProvider = true,
9192
documentOnTypeFormattingProvider = new DocumentOnTypeFormattingOptions {
9293
firstTriggerCharacter = "\n",
9394
moreTriggerCharacter = new[] { ";", ":" }

src/LanguageServer/Impl/LanguageServer.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,14 @@ public async Task<Reference[]> GotoDefinition(JToken token, CancellationToken ca
233233
}
234234
}
235235

236+
[JsonRpcMethod("textDocument/declaration")]
237+
public async Task<Location> GotoDeclaration(JToken token, CancellationToken cancellationToken) {
238+
using (_requestTimer.Time("textDocument/declaration")) {
239+
await _prioritizer.DefaultPriorityAsync(cancellationToken);
240+
return await _server.GotoDeclaration(ToObject<TextDocumentPositionParams>(token), GetToken(cancellationToken));
241+
}
242+
}
243+
236244
[JsonRpcMethod("textDocument/references")]
237245
public async Task<Reference[]> FindReferences(JToken token, CancellationToken cancellationToken) {
238246
using (_requestTimer.Time("textDocument/references")) {

src/LanguageServer/Impl/Protocol/Classes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ public sealed class ServerCapabilities {
448448
public DocumentOnTypeFormattingOptions documentOnTypeFormattingProvider;
449449
public bool renameProvider;
450450
public DocumentLinkOptions documentLinkProvider;
451+
public bool declarationProvider; // 3.14.0+
451452
public ExecuteCommandOptions executeCommandProvider;
452453
public object experimental;
453454
}

src/LanguageServer/Impl/Sources/DefinitionSource.cs

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,41 @@
2929
using Microsoft.Python.Parsing.Ast;
3030

3131
namespace Microsoft.Python.LanguageServer.Sources {
32-
internal sealed class DefinitionSource {
32+
/// <summary>
33+
/// Implements location of symbol declaration, such as 'B' in 'from A import B'
34+
/// statement in the file. For 'goto definition' behavior see <see cref="DefinitionSource"/>.
35+
/// </summary>
36+
internal sealed class DeclarationSource : DefinitionSourceBase {
37+
public DeclarationSource(IServiceContainer services) : base(services) { }
38+
protected override ILocatedMember GetDefiningMember(IMember m) => m as ILocatedMember;
39+
}
40+
41+
/// <summary>
42+
/// Implements location of symbol definition. For example, in 'from A import B'
43+
/// locates actual code of 'B' in module A. For 'goto declaration' behavior
44+
/// see <see cref="DeclarationSource"/>.
45+
/// </summary>
46+
internal sealed class DefinitionSource : DefinitionSourceBase {
47+
public DefinitionSource(IServiceContainer services) : base(services) { }
48+
protected override ILocatedMember GetDefiningMember(IMember m) => (m as ILocatedMember)?.GetRootDefinition();
49+
}
50+
51+
internal abstract class DefinitionSourceBase {
3352
private readonly IServiceContainer _services;
3453

35-
public DefinitionSource(IServiceContainer services) {
54+
protected DefinitionSourceBase(IServiceContainer services) {
3655
_services = services;
3756
}
3857

58+
protected abstract ILocatedMember GetDefiningMember(IMember m);
59+
60+
/// <summary>
61+
/// Locates definition or declaration of a symbol at the provided location.
62+
/// </summary>
63+
/// <param name="analysis">Document analysis.</param>
64+
/// <param name="location">Location in the document.</param>
65+
/// <param name="definingMember">Member location or null of not found.</param>
66+
/// <returns>Definition location (module URI and the text range).</returns>
3967
public Reference FindDefinition(IDocumentAnalysis analysis, SourceLocation location, out ILocatedMember definingMember) {
4068
definingMember = null;
4169
if (analysis?.Ast == null) {
@@ -171,11 +199,12 @@ private Reference TryFromVariable(string name, IDocumentAnalysis analysis, Sourc
171199
definingMember = v;
172200
if (statement is ImportStatement || statement is FromImportStatement) {
173201
// If we are on the variable definition in this module,
174-
// then goto definition should go to the parent, if any.
202+
// then goto declaration should go to the parent, if any.
203+
// Goto to definition navigates to the very root of the parent chain.
175204
var indexSpan = v.Definition.Span.ToIndexSpan(analysis.Ast);
176205
var index = location.ToIndex(analysis.Ast);
177206
if (indexSpan.Start <= index && index < indexSpan.End) {
178-
var parent = (v as IImportedMember)?.Parent;
207+
var parent = GetDefiningMember((v as IImportedMember)?.Parent);
179208
var definition = parent?.Definition ?? (v.Value as ILocatedMember)?.Definition;
180209
if (definition != null && CanNavigateToModule(definition.DocumentUri)) {
181210
return new Reference { range = definition.Span, uri = definition.DocumentUri };
@@ -227,7 +256,7 @@ private Reference FromMemberExpression(MemberExpression mex, IDocumentAnalysis a
227256
}
228257

229258
private Reference FromMember(IMember m) {
230-
var definition = (m as ILocatedMember)?.Definition;
259+
var definition = GetDefiningMember(m)?.Definition;
231260
var moduleUri = definition?.DocumentUri;
232261
// Make sure module we are looking for is not a stub
233262
if (m is IPythonType t) {

src/LanguageServer/Impl/Sources/ReferenceSource.cs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public async Task<Reference[]> FindAllReferencesAsync(Uri uri, SourceLocation lo
5454
return Array.Empty<Reference>();
5555
}
5656

57-
var rootDefinition = GetRootDefinition(definingMember);
57+
var rootDefinition = definingMember.GetRootDefinition();
5858
var name = definingMember.GetName();
5959

6060
// If it is an implicitly declared variable, such as function or a class
@@ -83,7 +83,7 @@ private async Task<Reference[]> FindAllReferencesAsync(string name, IPythonModul
8383
return Array.Empty<Reference>();
8484
}
8585

86-
rootDefinition = GetRootDefinition(definingMember);
86+
rootDefinition = definingMember.GetRootDefinition();
8787
}
8888

8989
return rootDefinition.References
@@ -151,20 +151,5 @@ private async Task<bool> AnalyzeFiles(IModuleManagement moduleManagement, IEnume
151151

152152
return analysisTasks.Count > 0;
153153
}
154-
155-
private ILocatedMember GetRootDefinition(ILocatedMember lm) {
156-
if (!(lm is IImportedMember im) || im.Parent == null) {
157-
return lm;
158-
}
159-
160-
var parent = im.Parent;
161-
for (; parent != null;) {
162-
if (!(parent is IImportedMember im1) || im1.Parent == null) {
163-
break;
164-
}
165-
parent = im1.Parent;
166-
}
167-
return parent;
168-
}
169154
}
170155
}

src/LanguageServer/Test/GoToDefinitionTests.cs

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ import logging as log
158158
}
159159

160160
[TestMethod, Priority(0)]
161-
public async Task GotoModuleSourceFromImport1() {
161+
public async Task GotoModuleSourceFromImport() {
162162
const string code = @"from logging import A";
163163
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
164164
var ds = new DefinitionSource(Services);
@@ -171,14 +171,34 @@ public async Task GotoModuleSourceFromImport1() {
171171
}
172172

173173
[TestMethod, Priority(0)]
174-
public async Task GotoModuleSourceFromImport2() {
174+
public async Task GotoDefitionFromImport() {
175175
const string code = @"
176176
from MultiValues import t
177177
x = t
178178
";
179179
var analysis = await GetAnalysisAsync(code);
180180
var ds = new DefinitionSource(Services);
181181

182+
var reference = ds.FindDefinition(analysis, new SourceLocation(3, 5), out _);
183+
reference.Should().NotBeNull();
184+
reference.range.Should().Be(2, 0, 2, 1);
185+
reference.uri.AbsolutePath.Should().Contain("MultiValues.py");
186+
187+
reference = ds.FindDefinition(analysis, new SourceLocation(2, 25), out _);
188+
reference.Should().NotBeNull();
189+
reference.range.Should().Be(2, 0, 2, 1);
190+
reference.uri.AbsolutePath.Should().Contain("MultiValues.py");
191+
}
192+
193+
[TestMethod, Priority(0)]
194+
public async Task GotoDeclarationFromImport() {
195+
const string code = @"
196+
from MultiValues import t
197+
x = t
198+
";
199+
var analysis = await GetAnalysisAsync(code);
200+
var ds = new DeclarationSource(Services);
201+
182202
var reference = ds.FindDefinition(analysis, new SourceLocation(3, 5), out _);
183203
reference.Should().NotBeNull();
184204
reference.range.Should().Be(1, 24, 1, 25);
@@ -202,16 +222,50 @@ public async Task GotoModuleSourceFromImportAs() {
202222
reference.uri.AbsolutePath.Should().NotContain("pyi");
203223
}
204224

225+
[TestMethod, Priority(0)]
226+
public async Task GotoDefinitionFromImportAs() {
227+
const string code = @"
228+
from logging import critical as crit
229+
x = crit
230+
";
231+
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
232+
var ds = new DefinitionSource(Services);
233+
234+
var reference = ds.FindDefinition(analysis, new SourceLocation(3, 6), out _);
235+
reference.Should().NotBeNull();
236+
reference.range.start.line.Should().BeGreaterThan(500);
237+
reference.uri.AbsolutePath.Should().Contain("logging");
238+
reference.uri.AbsolutePath.Should().NotContain("pyi");
239+
}
240+
241+
[TestMethod, Priority(0)]
242+
public async Task GotoDeclarationFromImportAs() {
243+
const string code = @"
244+
from logging import critical as crit
245+
x = crit
246+
";
247+
var analysis = await GetAnalysisAsync(code, PythonVersions.LatestAvailable3X);
248+
var ds = new DeclarationSource(Services);
249+
250+
var reference = ds.FindDefinition(analysis, new SourceLocation(3, 6), out _);
251+
reference.Should().NotBeNull();
252+
reference.range.Should().Be(1, 32, 1, 36);
253+
}
254+
205255
[TestMethod, Priority(0)]
206256
public async Task GotoBuiltinObject() {
207257
const string code = @"
208258
class A(object):
209259
pass
210260
";
211261
var analysis = await GetAnalysisAsync(code);
212-
var ds = new DefinitionSource(Services);
213262

214-
var reference = ds.FindDefinition(analysis, new SourceLocation(2, 12), out _);
263+
var ds1 = new DefinitionSource(Services);
264+
var reference = ds1.FindDefinition(analysis, new SourceLocation(2, 12), out _);
265+
reference.Should().BeNull();
266+
267+
var ds2 = new DeclarationSource(Services);
268+
reference = ds2.FindDefinition(analysis, new SourceLocation(2, 12), out _);
215269
reference.Should().BeNull();
216270
}
217271

0 commit comments

Comments
 (0)