diff --git a/src/LanguageServer/Impl/Sources/DefinitionSource.cs b/src/LanguageServer/Impl/Sources/DefinitionSource.cs index f65ccb4c3..a44248797 100644 --- a/src/LanguageServer/Impl/Sources/DefinitionSource.cs +++ b/src/LanguageServer/Impl/Sources/DefinitionSource.cs @@ -38,7 +38,7 @@ public DefinitionSource(IServiceContainer services) { public Reference FindDefinition(IDocumentAnalysis analysis, SourceLocation location, out ILocatedMember member) { member = null; - if(analysis?.Ast == null) { + if (analysis?.Ast == null) { return null; } @@ -116,26 +116,41 @@ private Reference FromMemberExpression(MemberExpression mex, IDocumentAnalysis a var target = eval.GetValueFromExpression(mex.Target); var type = target?.GetPythonType(); - if (type?.GetMember(mex.Name) is ILocatedMember lm) { - member = lm; - return FromMember(lm); - } + switch (type) { + case IPythonModule m when m.Analysis.GlobalScope != null: + // Module GetMember returns module variable value while we + // want the variable itself since we want to know its location. + var v1 = m.Analysis.GlobalScope.Variables[mex.Name]; + if (v1 != null) { + member = v1; + return FromMember(v1); + } + break; - if (type is IPythonClassType cls) { - // Data members may be instances that are not tracking locations. - // In this case we'll try look up the respective variable instead. - using (eval.OpenScope(analysis.Document, cls.ClassDefinition)) { - eval.LookupNameInScopes(mex.Name, out _, out var v, LookupOptions.Local); - if (v != null) { - member = v; - return FromMember(v); + case IPythonClassType cls: + // Data members may be instances that are not tracking locations. + // In this case we'll try look up the respective variable instead. + using (eval.OpenScope(analysis.Document, cls.ClassDefinition)) { + eval.LookupNameInScopes(mex.Name, out _, out var v2, LookupOptions.Local); + if (v2 != null) { + member = v2; + return FromMember(v2); + } } - } + break; + + default: + if (type?.GetMember(mex.Name) is ILocatedMember lm) { + member = lm; + return FromMember(lm); + } + + break; } return null; } - private Reference TryFromImport(Node statement, string name, IDocumentAnalysis analysis, out IMember value) { + private static Reference TryFromImport(Node statement, string name, IDocumentAnalysis analysis, out IMember value) { value = null; string moduleName = null; switch (statement) { diff --git a/src/LanguageServer/Test/GoToDefinitionTests.cs b/src/LanguageServer/Test/GoToDefinitionTests.cs index 323dc2e44..a1b9decbf 100644 --- a/src/LanguageServer/Test/GoToDefinitionTests.cs +++ b/src/LanguageServer/Test/GoToDefinitionTests.cs @@ -262,5 +262,20 @@ import re reference.uri.AbsolutePath.Should().Contain("re.py"); reference.uri.AbsolutePath.Should().NotContain("pyi"); } + + [TestMethod, Priority(0)] + public async Task Constant() { + const string code = @" +import helper +helper.message +"; + var analysis = await GetAnalysisAsync(code); + var ds = new DefinitionSource(Services); + + var reference = ds.FindDefinition(analysis, new SourceLocation(3, 11), out _); + reference.Should().NotBeNull(); + reference.range.Should().Be(0, 0, 0, 7); + reference.uri.AbsolutePath.Should().Contain("__init__.py"); + } } } diff --git a/src/UnitTests/TestData/AstAnalysis/helper/__init__.py b/src/UnitTests/TestData/AstAnalysis/helper/__init__.py new file mode 100644 index 000000000..ad41abc66 --- /dev/null +++ b/src/UnitTests/TestData/AstAnalysis/helper/__init__.py @@ -0,0 +1 @@ +message = 'HELLO'