diff --git a/src/Analysis/Engine/Test/AnalysisTest.cs b/src/Analysis/Engine/Test/AnalysisTest.cs index 1c3bf74ac..a45a781f8 100644 --- a/src/Analysis/Engine/Test/AnalysisTest.cs +++ b/src/Analysis/Engine/Test/AnalysisTest.cs @@ -38,7 +38,7 @@ namespace AnalysisTests { [TestClass] - public partial class AnalysisTest { + public class AnalysisTest { public TestContext TestContext { get; set; } [TestInitialize] @@ -864,102 +864,6 @@ import mod2 } */ - [TestMethod, Priority(0)] - public async Task PrivateMembers() { - using (var server = await CreateServerAsync(PythonVersions.LatestAvailable2X)) { - var uri = TestData.GetTempPathUri("test-module.py"); - - string code = @" -class C: - def __init__(self): - self._C__X = 'abc' # Completions here should only ever show __X - self.__X = 42 - -class D(C): - def __init__(self): - print(self.__X) # self. here shouldn't have __X or _C__X (could be controlled by Text Editor->Python->general->Hide advanced members to show _C__X) -"; - - await server.SendDidOpenTextDocument(uri, code); - await server.GetAnalysisAsync(uri); - - var completions = await server.SendCompletion(uri, 4, 13); - completions.Should().OnlyHaveLabels("__X", "__init__", "__doc__", "__class__"); - - completions = await server.SendCompletion(uri, 8, 19); - completions.Should().OnlyHaveLabels("_C__X", "__init__", "__doc__", "__class__"); - - code = @" -class C(object): - def __init__(self): - self.f(_C__A = 42) # sig help should be _C__A - - def f(self, __A): - pass - - -class D(C): - def __init__(self): - self.f(_C__A=42) # sig help should be _C__A -"; - - await server.SendDidChangeTextDocumentAsync(uri, code); - await server.GetAnalysisAsync(uri); - - var signatures = await server.SendSignatureHelp(uri, 3, 15); - signatures.Should().HaveSingleSignature() - .Which.Should().OnlyHaveParameterLabels("_C__A"); - - signatures = await server.SendSignatureHelp(uri, 11, 15); - signatures.Should().HaveSingleSignature() - .Which.Should().OnlyHaveParameterLabels("_C__A"); - - code = @" -class C(object): - def __init__(self): - self.__f(_C__A = 42) # member should be __f - - def __f(self, __A): - pass - - -class D(C): - def __init__(self): - self._C__f(_C__A=42) # member should be _C__f - -"; - - await server.SendDidChangeTextDocumentAsync(uri, code); - await server.GetAnalysisAsync(uri); - - completions = await server.SendCompletion(uri, 3, 13); - completions.Should().HaveLabels("__f", "__init__"); - - completions = await server.SendCompletion(uri, 11, 13); - completions.Should().HaveLabels("_C__f", "__init__"); - - code = @" -class C(object): - __FOB = 42 - - def f(self): - abc = C.__FOB # Completion should work here - - -xyz = C._C__FOB # Advanced members completion should work here -"; - - await server.SendDidChangeTextDocumentAsync(uri, code); - await server.GetAnalysisAsync(uri); - - completions = await server.SendCompletion(uri, 5, 16); - completions.Should().HaveLabels("__FOB", "f"); - - completions = await server.SendCompletion(uri, 8, 8); - completions.Should().HaveLabels("_C__FOB", "f"); - } - } - [TestMethod, Priority(0)] public async Task BaseInstanceVariable() { var code = @" @@ -3975,28 +3879,6 @@ def f(x = x): } } - [TestMethod, Priority(0)] - public async Task RecursiveClass() { - var code = @" -cls = object - -class cls(cls): - abc = 42 - -a = cls().abc -b = cls.abc -"; - using (var server = await CreateServerAsync()) { - var analysis = await server.OpenDefaultDocumentAndGetAnalysisAsync(code); - var completion = await server.SendCompletion(TestData.GetDefaultModuleUri(), 8, 0); - - analysis.Should().HaveVariable("a").OfType(BuiltinTypeId.Int) - .And.HaveVariable("b").OfType(BuiltinTypeId.Int); - - completion.Should().HaveLabels("cls", "object"); - } - } - [TestMethod, Priority(0)] public async Task BadMethod() { var code = @" @@ -4134,43 +4016,6 @@ public async Task KeywordSplat(string functionDeclaration, int signatureLine, st } } - [TestMethod, Priority(0)] - public async Task ForwardRef() { - var code = @" - -class D(object): - def oar(self, x): - abc = C() - abc.fob(2) - a = abc.fob(2.0) - a.oar(('a', 'b', 'c', 'd')) - -class C(object): - def fob(self, x): - D().oar('abc') - D().oar(['a', 'b', 'c']) - return D() - def baz(self): pass -"; - using (var server = await CreateServerAsync(PythonVersions.LatestAvailable2X)) { - var analysis = await server.OpenDefaultDocumentAndGetAnalysisAsync(code); - var completionInD = await server.SendCompletion(TestData.GetDefaultModuleUri(), 3, 4); - var completionInOar = await server.SendCompletion(TestData.GetDefaultModuleUri(), 5, 8); - var completionForAbc = await server.SendCompletion(TestData.GetDefaultModuleUri(), 5, 12); - - completionInD.Should().HaveLabels("C", "D", "oar") - .And.NotContainLabels("a", "abc", "self", "x", "fob", "baz"); - - completionInOar.Should().HaveLabels("C", "D", "a", "oar", "abc", "self", "x") - .And.NotContainLabels("fob", "baz"); - - completionForAbc.Should().HaveLabels("baz", "fob"); - - analysis.Should().HaveClass("D").WithFunction("oar") - .Which.Should().HaveParameter("x").OfTypes(BuiltinTypeId.List, BuiltinTypeId.Str, BuiltinTypeId.Tuple); - } - } - [TestMethod, Priority(0)] public async Task Builtins() { @@ -4277,28 +4122,6 @@ def g(a, b): } } - [TestMethod, Priority(0)] - public async Task SimpleGlobals() { - var code = @" -class x(object): - def abc(self): - pass - -a = x() -x.abc() -"; - using (var server = await CreateServerAsync()) { - var objectMemberNames = server.GetBuiltinTypeMemberNames(BuiltinTypeId.Object); - - var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); - var completion = await server.SendCompletion(uri, 6, 0); - var completionX = await server.SendCompletion(uri, 6, 2); - - completion.Should().HaveLabels("a", "x").And.NotContainLabels("abc", "self"); - completionX.Should().HaveLabels(objectMemberNames).And.HaveLabels("abc"); - } - } - [TestMethod, Priority(0)] public async Task FuncCallInIf() { var code = @" @@ -4403,67 +4226,6 @@ def f(a, b, c=0): } } - /// - /// http://pytools.codeplex.com/workitem/799 - /// - [TestMethod, Priority(0)] - public async Task OverrideCompletions2X() { - var code = @" -class oar(list): - def - pass -"; - using (var server = await CreateServerAsync(PythonVersions.LatestAvailable2X)) { - var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); - var completions = await server.SendCompletion(uri, 2, 8); - - completions.Should().HaveItem("append") - .Which.Should().HaveInsertText("append(self, value):\r\n return super(oar, self).append(value)"); - } - } - - [DataRow(PythonLanguageVersion.V36, "value")] - [DataRow(PythonLanguageVersion.V37, "object")] - [DataTestMethod, Priority(0)] - public async Task OverrideCompletions3X(PythonLanguageVersion version, string parameterName) { - var code = @" -class oar(list): - def - pass -"; - using (var server = await CreateServerAsync(PythonVersions.GetRequiredCPythonConfiguration(version))) { - var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); - var completions = await server.SendCompletion(uri, 2, 8); - - completions.Should().HaveItem("append") - .Which.Should().HaveInsertText($"append(self, {parameterName}):\r\n return super().append({parameterName})"); - } - } - - [TestMethod, Priority(0)] - public async Task OverrideCompletionsNested() { - // Ensure that nested classes are correctly resolved. - var code = @" -class oar(int): - class fob(dict): - def - pass - def - pass -"; - - using (var server = await CreateServerAsync(PythonVersions.LatestAvailable2X)) { - var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); - var completionsOar = await server.SendCompletion(uri, 5, 8); - var completionsFob = await server.SendCompletion(uri, 3, 12); - - completionsOar.Should().NotContainLabels("keys", "items") - .And.HaveItem("bit_length"); - completionsFob.Should().NotContainLabels("bit_length") - .And.HaveLabels("keys", "items"); - } - } - /// /// https://github.com/Microsoft/PTVS/issues/995 /// @@ -6618,32 +6380,6 @@ def with_params_default_starargs(*args, **kwargs): .And.HaveVariable("rf").WithDescription("method return_func of module.return_func_class objects...") .WithDocumentation("some help"); } - - - } - - [TestMethod, Priority(0)] - public async Task CompletionDocumentation() { - var text = @" -import sys -z = 43 - -class fob(object): - @property - def f(self): pass - - def g(self): pass - -d = fob() -"; - using (var server = await CreateServerAsync()) { - var uri = await server.OpenDefaultDocumentAndGetUriAsync(text); - var completionD = await server.SendCompletion(uri, 15, 1); - completionD.Should().HaveItem("d") - .Which.Should().HaveDocumentation("fob"); - completionD.Should().HaveItem("z") - .Which.Should().HaveDocumentation("int"); - } } [TestMethod, Priority(0)] @@ -6848,73 +6584,6 @@ print abc } } - [TestMethod, Priority(0)] - public async Task TypeAtEndOfMethod() { - var text = @" -class Fob(object): - def oar(self, a): - pass - - - def fob(self): - pass - -x = Fob() -x.oar(100) -"; - - using (var server = await CreateServerAsync()) { - var uri = await server.OpenDefaultDocumentAndGetUriAsync(text); - var completion = await server.SendCompletion(uri, 5, 8); - completion.Should().HaveItem("a") - .Which.Should().HaveDocumentation("int"); - } - } - - [TestMethod, Priority(0)] - public async Task TypeAtEndOfIncompleteMethod() { - var text = @" -class Fob(object): - def oar(self, a): - - - - - -x = Fob() -x.oar(100) -"; - - using (var server = await CreateServerAsync()) { - var uri = await server.OpenDefaultDocumentAndGetUriAsync(text); - var completion = await server.SendCompletion(uri, 5, 8); - completion.Should().HaveItem("a") - .Which.Should().HaveDocumentation("int"); - } - } - - [TestMethod, Priority(0)] - public async Task TypeIntersectionUserDefinedTypes() { - var text = @" -class C1(object): - def fob(self): pass - -class C2(object): - def oar(self): pass - -c = C1() -c.fob() -c = C2() -c. -"; - - using (var server = await CreateServerAsync()) { - var uri = await server.OpenDefaultDocumentAndGetUriAsync(text); - var completion = await server.SendCompletion(uri, 10, 2); - completion.Should().NotContainLabels("fob", "oar"); - } - } - [TestMethod, Priority(0)] public async Task UpdateMethodMultiFiles() { var text1 = @" diff --git a/src/Analysis/Engine/Test/CompletionTest.cs b/src/Analysis/Engine/Test/CompletionTest.cs new file mode 100644 index 000000000..9009a7c26 --- /dev/null +++ b/src/Analysis/Engine/Test/CompletionTest.cs @@ -0,0 +1,413 @@ +// Python Tools for Visual Studio +// Copyright(c) Microsoft Corporation +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the License); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System.Threading.Tasks; +using Microsoft.Python.LanguageServer; +using Microsoft.Python.LanguageServer.Implementation; +using Microsoft.PythonTools.Analysis; +using Microsoft.PythonTools.Analysis.FluentAssertions; +using Microsoft.PythonTools.Analysis.Infrastructure; +using Microsoft.PythonTools.Interpreter; +using Microsoft.PythonTools.Parsing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace AnalysisTests { + [TestClass] + public class CompletionTest { + public TestContext TestContext { get; set; } + + [TestInitialize] + public void TestInitialize() { + TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}"); + } + + [TestCleanup] + public void TestCleanup() { + TestEnvironmentImpl.TestCleanup(); + } + + [ServerTestMethod, Priority(0)] + public async Task CompletionInWithStatementDerivedClass(Server server) { + var uri = await server.OpenDefaultDocumentAndGetUriAsync("with open(x) as fs:\n fs. "); + await server.GetAnalysisAsync(uri); + var completions = await server.SendCompletion(uri, 1, 5); + + completions.Should().HaveLabels("read", "write"); + await server.UnloadFileAsync(uri); + } + + [ServerTestMethod(LatestAvailable2X = true), Priority(0)] + public async Task PrivateMembers(Server server) { + var uri = TestData.GetTempPathUri("test-module.py"); + + string code = @" +class C: + def __init__(self): + self._C__X = 'abc' # Completions here should only ever show __X + self.__X = 42 + +class D(C): + def __init__(self): + print(self.__X) # self. here shouldn't have __X or _C__X (could be controlled by Text Editor->Python->general->Hide advanced members to show _C__X) +"; + + await server.SendDidOpenTextDocument(uri, code); + await server.GetAnalysisAsync(uri); + + var completions = await server.SendCompletion(uri, 4, 13); + completions.Should().OnlyHaveLabels("__X", "__init__", "__doc__", "__class__"); + + completions = await server.SendCompletion(uri, 8, 19); + completions.Should().OnlyHaveLabels("_C__X", "__init__", "__doc__", "__class__"); + + code = @" +class C(object): + def __init__(self): + self.f(_C__A = 42) # sig help should be _C__A + + def f(self, __A): + pass + + +class D(C): + def __init__(self): + self.f(_C__A=42) # sig help should be _C__A +"; + + await server.SendDidChangeTextDocumentAsync(uri, code); + await server.GetAnalysisAsync(uri); + + var signatures = await server.SendSignatureHelp(uri, 3, 15); + signatures.Should().HaveSingleSignature() + .Which.Should().OnlyHaveParameterLabels("_C__A"); + + signatures = await server.SendSignatureHelp(uri, 11, 15); + signatures.Should().HaveSingleSignature() + .Which.Should().OnlyHaveParameterLabels("_C__A"); + + code = @" +class C(object): + def __init__(self): + self.__f(_C__A = 42) # member should be __f + + def __f(self, __A): + pass + + +class D(C): + def __init__(self): + self._C__f(_C__A=42) # member should be _C__f + +"; + + await server.SendDidChangeTextDocumentAsync(uri, code); + await server.GetAnalysisAsync(uri); + + completions = await server.SendCompletion(uri, 3, 13); + completions.Should().HaveLabels("__f", "__init__"); + + completions = await server.SendCompletion(uri, 11, 13); + completions.Should().HaveLabels("_C__f", "__init__"); + + code = @" +class C(object): + __FOB = 42 + + def f(self): + abc = C.__FOB # Completion should work here + + +xyz = C._C__FOB # Advanced members completion should work here +"; + + await server.SendDidChangeTextDocumentAsync(uri, code); + await server.GetAnalysisAsync(uri); + + completions = await server.SendCompletion(uri, 5, 16); + completions.Should().HaveLabels("__FOB", "f"); + + completions = await server.SendCompletion(uri, 8, 8); + completions.Should().HaveLabels("_C__FOB", "f"); + } + + [ServerTestMethod, Priority(0)] + public async Task RecursiveClass(Server server) { + var code = @" +cls = object + +class cls(cls): + abc = 42 + +a = cls().abc +b = cls.abc +"; + + var analysis = await server.OpenDefaultDocumentAndGetAnalysisAsync(code); + var completion = await server.SendCompletion(TestData.GetDefaultModuleUri(), 8, 0); + + analysis.Should().HaveVariable("a").OfType(BuiltinTypeId.Int) + .And.HaveVariable("b").OfType(BuiltinTypeId.Int); + + completion.Should().HaveLabels("cls", "object"); + } + + [ServerTestMethod, Priority(0)] + public async Task ForwardRef(Server server) { + var code = @" + +class D(object): + def oar(self, x): + abc = C() + abc.fob(2) + a = abc.fob(2.0) + a.oar(('a', 'b', 'c', 'd')) + +class C(object): + def fob(self, x): + D().oar('abc') + D().oar(['a', 'b', 'c']) + return D() + def baz(self): pass +"; + + var analysis = await server.OpenDefaultDocumentAndGetAnalysisAsync(code); + var completionInD = await server.SendCompletion(TestData.GetDefaultModuleUri(), 3, 4); + var completionInOar = await server.SendCompletion(TestData.GetDefaultModuleUri(), 5, 8); + var completionForAbc = await server.SendCompletion(TestData.GetDefaultModuleUri(), 5, 12); + + completionInD.Should().HaveLabels("C", "D", "oar") + .And.NotContainLabels("a", "abc", "self", "x", "fob", "baz"); + + completionInOar.Should().HaveLabels("C", "D", "a", "oar", "abc", "self", "x") + .And.NotContainLabels("fob", "baz"); + + completionForAbc.Should().HaveLabels("baz", "fob"); + + analysis.Should().HaveClass("D").WithFunction("oar") + .Which.Should().HaveParameter("x").OfTypes(BuiltinTypeId.List, BuiltinTypeId.Str, BuiltinTypeId.Tuple); + } + + [ServerTestMethod, Priority(0)] + public async Task SimpleGlobals(Server server) { + var code = @" +class x(object): + def abc(self): + pass + +a = x() +x.abc() +"; + var objectMemberNames = server.GetBuiltinTypeMemberNames(BuiltinTypeId.Object); + + var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); + var completion = await server.SendCompletion(uri, 6, 0); + var completionX = await server.SendCompletion(uri, 6, 2); + + completion.Should().HaveLabels("a", "x").And.NotContainLabels("abc", "self"); + completionX.Should().HaveLabels(objectMemberNames).And.HaveLabels("abc"); + } + + /// + /// http://pytools.codeplex.com/workitem/799 + /// + [ServerTestMethod(LatestAvailable2X = true), Priority(0)] + public async Task OverrideCompletions2X(Server server) { + var code = @" +class oar(list): + def + pass +"; + var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); + var completions = await server.SendCompletion(uri, 2, 8); + + completions.Should().HaveItem("append") + .Which.Should().HaveInsertText("append(self, value):\r\n return super(oar, self).append(value)"); + } + + [DataRow(PythonLanguageVersion.V36, "value")] + [DataRow(PythonLanguageVersion.V37, "object")] + [ServerTestMethod(VersionArgumentIndex = 1), Priority(0)] + public async Task OverrideCompletions3X(Server server, PythonLanguageVersion version, string parameterName) { + var code = @" +class oar(list): + def + pass +"; + var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); + var completions = await server.SendCompletion(uri, 2, 8); + + completions.Should().HaveItem("append") + .Which.Should().HaveInsertText($"append(self, {parameterName}):\r\n return super().append({parameterName})"); + } + + [ServerTestMethod(LatestAvailable2X = true), Priority(0)] + public async Task OverrideCompletionsNested(Server server) { + // Ensure that nested classes are correctly resolved. + var code = @" +class oar(int): + class fob(dict): + def + pass + def + pass +"; + + + var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); + var completionsOar = await server.SendCompletion(uri, 5, 8); + var completionsFob = await server.SendCompletion(uri, 3, 12); + + completionsOar.Should().NotContainLabels("keys", "items") + .And.HaveItem("bit_length"); + completionsFob.Should().NotContainLabels("bit_length") + .And.HaveLabels("keys", "items"); + } + + [ServerTestMethod, Priority(0)] + public async Task CompletionDocumentation(Server server) { + var text = @" +import sys +z = 43 + +class fob(object): + @property + def f(self): pass + + def g(self): pass + +d = fob() +"; + var uri = await server.OpenDefaultDocumentAndGetUriAsync(text); + var completionD = await server.SendCompletion(uri, 15, 1); + completionD.Should().HaveItem("d") + .Which.Should().HaveDocumentation("fob"); + completionD.Should().HaveItem("z") + .Which.Should().HaveDocumentation("int"); + } + + [ServerTestMethod, Priority(0)] + public async Task TypeAtEndOfMethod(Server server) { + var text = @" +class Fob(object): + def oar(self, a): + pass + + + def fob(self): + pass + +x = Fob() +x.oar(100) +"; + + var uri = await server.OpenDefaultDocumentAndGetUriAsync(text); + var completion = await server.SendCompletion(uri, 5, 8); + completion.Should().HaveItem("a") + .Which.Should().HaveDocumentation("int"); + } + + [ServerTestMethod, Priority(0)] + public async Task TypeAtEndOfIncompleteMethod(Server server) { + var text = @" +class Fob(object): + def oar(self, a): + + + + + +x = Fob() +x.oar(100) +"; + + var uri = await server.OpenDefaultDocumentAndGetUriAsync(text); + var completion = await server.SendCompletion(uri, 5, 8); + completion.Should().HaveItem("a") + .Which.Should().HaveDocumentation("int"); + } + + [ServerTestMethod, Priority(0)] + public async Task TypeIntersectionUserDefinedTypes(Server server) { + var text = @" +class C1(object): + def fob(self): pass + +class C2(object): + def oar(self): pass + +c = C1() +c.fob() +c = C2() +c. +"; + + + var uri = await server.OpenDefaultDocumentAndGetUriAsync(text); + var completion = await server.SendCompletion(uri, 10, 2); + completion.Should().NotContainLabels("fob", "oar"); + } + + [DataRow(@" +def foo(): + pass + +", 3, 0, "foo", "foo($0)")] + [DataRow(@" +def foo(): + pass +fo +", 3, 2, "foo", "foo($0)")] + [DataRow(@" +def foo(value): + pass + +", 3, 0, "foo", "foo($0)")] + [DataRow(@" +def foo(value): + pass + +", 3, 0, "min", "min($0)")] + [DataRow(@" +class Class1(object): + def foo(self): + pass +Class1(). +", 4, 9, "foo", "foo($0)")] + [DataRow(@" +class Class1(object): + def foo(self, value): + pass +Class1(). +", 4, 9, "foo", "foo($0)")] + [DataRow(@" +class Class1(list): + def foo(self): + pass +Class1(). +", 4, 9, "append", "append($0)")] + [ServerTestMethod, Priority(0)] + public async Task Completion_AddBracketsEnabled(Server server, string code, int row, int character, string expectedLabel, string expectedInsertText) { + await server.SendDidChangeConfiguration(new ServerSettings.PythonCompletionOptions {addBrackets = true}); + + var uri = await server.OpenDefaultDocumentAndGetUriAsync(code); + var completion = await server.SendCompletion(uri, row, character); + + completion.Should().HaveItem(expectedLabel) + .Which.Should().HaveInsertText(expectedInsertText); + } + } +} \ No newline at end of file diff --git a/src/Analysis/Engine/Test/FluentAssertions/CompletionListAssertions.cs b/src/Analysis/Engine/Test/FluentAssertions/CompletionListAssertions.cs index 9617df065..c5e7f5dd1 100644 --- a/src/Analysis/Engine/Test/FluentAssertions/CompletionListAssertions.cs +++ b/src/Analysis/Engine/Test/FluentAssertions/CompletionListAssertions.cs @@ -42,7 +42,7 @@ public AndConstraint OnlyHaveLabels(IEnumerable i.label).ToArray() ?? new string[0]; var expected = labels.ToArray(); - var errorMessage = GetAssertCollectionOnlyContainsMessage(actual, expected, "completion list items", "label", "labels"); + var errorMessage = GetAssertCollectionOnlyContainsMessage(actual, expected, GetName(), "label", "labels"); Execute.Assertion.ForCondition(errorMessage == null) .BecauseOf(because, reasonArgs) @@ -56,7 +56,7 @@ public AndWhichConstraint HaveItem(str NotBeNull(because, reasonArgs); var actual = Subject?.items.Where(i => string.Equals(i.label, label, StringComparison.Ordinal)).ToArray() ?? Array.Empty(); - var errorMessage = GetAssertCollectionContainsMessage(actual.Select(i => i.label).ToArray(), new [] { label }, "completion list items", "label", "labels"); + var errorMessage = GetAssertCollectionContainsMessage(actual.Select(i => i.label).ToArray(), new [] { label }, GetName(), "label", "labels"); Execute.Assertion.ForCondition(errorMessage == null) .BecauseOf(because, reasonArgs) @@ -76,7 +76,7 @@ public AndConstraint HaveLabels(IEnumerable la var actual = Subject.items?.Select(i => i.label).ToArray() ?? new string[0]; var expected = labels.ToArray(); - var errorMessage = GetAssertCollectionContainsMessage(actual, expected, "completion list items", "label", "labels"); + var errorMessage = GetAssertCollectionContainsMessage(actual, expected, GetName(), "label", "labels"); Execute.Assertion.ForCondition(errorMessage == null) .BecauseOf(because, reasonArgs) @@ -96,7 +96,7 @@ public AndConstraint NotContainLabels(IEnumerable i.label).ToArray() ?? new string[0]; var expected = labels.ToArray(); - var errorMessage = GetAssertCollectionNotContainMessage(actual, expected, "completion list items", "label", "labels"); + var errorMessage = GetAssertCollectionNotContainMessage(actual, expected, GetName(), "label", "labels"); Execute.Assertion.ForCondition(errorMessage == null) .BecauseOf(because, reasonArgs) @@ -104,5 +104,8 @@ public AndConstraint NotContainLabels(IEnumerable(this); } + + [CustomAssertion] + private static string GetName() => CallerIdentifier.DetermineCallerIdentity() ?? "completion list items"; } } \ No newline at end of file diff --git a/src/Analysis/Engine/Test/LanguageServerTests.cs b/src/Analysis/Engine/Test/LanguageServerTests.cs index fae6da258..4cd1fdc16 100644 --- a/src/Analysis/Engine/Test/LanguageServerTests.cs +++ b/src/Analysis/Engine/Test/LanguageServerTests.cs @@ -385,18 +385,6 @@ public async Task CompletionInWithStatement() { await s.UnloadFileAsync(u); } - [TestMethod, Priority(0)] - public async Task CompletionInWithStatementDerivedClass() { - using (var server = await CreateServer()) { - var uri = await server.OpenDefaultDocumentAndGetUriAsync("with open(x) as fs:\n fs. "); - await server.GetAnalysisAsync(uri); - var completions = await server.SendCompletion(uri, 1, 5); - - completions.Should().HaveLabels("read", "write"); - await server.UnloadFileAsync(uri); - } - } - [TestMethod, Priority(0)] public async Task CompletionInImport() { var s = await CreateServer(); diff --git a/src/Analysis/Engine/Test/ServerExtensions.cs b/src/Analysis/Engine/Test/ServerExtensions.cs index 08615b5ff..cfc562917 100644 --- a/src/Analysis/Engine/Test/ServerExtensions.cs +++ b/src/Analysis/Engine/Test/ServerExtensions.cs @@ -186,6 +186,31 @@ public static Task SendDocumentOnTypeFormatting(this Server server, }, CancellationToken.None); } + public static Task SendDidChangeConfiguration(this Server server, ServerSettings.PythonCompletionOptions pythonCompletionOptions, int failAfter = 30000) { + var currentSettings = server.Settings; + var settings = new LanguageServerSettings(); + + settings.completion.showAdvancedMembers = pythonCompletionOptions.showAdvancedMembers; + settings.completion.addBrackets = pythonCompletionOptions.addBrackets; + + settings.analysis.openFilesOnly = currentSettings.analysis.openFilesOnly; + if (currentSettings is LanguageServerSettings languageServerSettings) { + settings.diagnosticPublishDelay = languageServerSettings.diagnosticPublishDelay; + settings.symbolsHierarchyDepthLimit = languageServerSettings.symbolsHierarchyDepthLimit; + } + + var errors = currentSettings.analysis.errors; + var warnings = currentSettings.analysis.warnings; + var information = currentSettings.analysis.information; + var disabled = currentSettings.analysis.disabled; + settings.analysis.SetErrorSeverityOptions(errors, warnings, information, disabled); + + return server.SendDidChangeConfiguration(settings, failAfter); + } + + public static Task SendDidChangeConfiguration(this Server server, ServerSettings settings, int failAfter = 30000) + => server.DidChangeConfiguration(new DidChangeConfigurationParams { settings = settings }, new CancellationTokenSource(failAfter).Token); + public static async Task ChangeDefaultDocumentAndGetAnalysisAsync(this Server server, string text, int failAfter = 30000) { var projectEntry = (ProjectEntry) server.ProjectFiles.Single(); await server.SendDidChangeTextDocumentAsync(projectEntry.DocumentUri, text); diff --git a/src/Analysis/Engine/Test/ServerTestMethodAttribute.cs b/src/Analysis/Engine/Test/ServerTestMethodAttribute.cs new file mode 100644 index 000000000..9bc4231d6 --- /dev/null +++ b/src/Analysis/Engine/Test/ServerTestMethodAttribute.cs @@ -0,0 +1,69 @@ +// Python Tools for Visual Studio +// Copyright(c) Microsoft Corporation +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the License); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System; +using System.Threading.Tasks; +using Microsoft.Python.LanguageServer.Implementation; +using Microsoft.PythonTools.Analysis; +using Microsoft.PythonTools.Interpreter; +using Microsoft.PythonTools.Parsing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace AnalysisTests { + public class ServerTestMethodAttribute : TestMethodAttribute { + public bool LatestAvailable2X { get; set; } + public int VersionArgumentIndex { get; set; } = -1; + + public override TestResult[] Execute(ITestMethod testMethod) { + if (testMethod.ParameterTypes[0].ParameterType == typeof(Server) && (testMethod.Arguments == null || testMethod.ParameterTypes.Length == testMethod.Arguments.Length + 1)) { + return new[] { ExecuteWithServer(testMethod) }; + } + + return base.Execute(testMethod); + } + + private TestResult ExecuteWithServer(ITestMethod testMethod) { + var arguments = ExtendArguments(testMethod.Arguments); + + TestEnvironmentImpl.AddBeforeAfterTest(async () => { + var interpreterConfiguration = GetInterpreterConfiguration(arguments); + var server = await new Server().InitializeAsync(interpreterConfiguration); + arguments[0] = server; + return server; + }); + + return testMethod.Invoke(arguments); + } + + private InterpreterConfiguration GetInterpreterConfiguration(object[] arguments) { + return VersionArgumentIndex >= 0 && VersionArgumentIndex < arguments.Length + ? PythonVersions.GetRequiredCPythonConfiguration((PythonLanguageVersion)arguments[VersionArgumentIndex]) + : LatestAvailable2X ? PythonVersions.LatestAvailable2X : PythonVersions.LatestAvailable; + } + + private static object[] ExtendArguments(object[] arguments) { + if (arguments == null || arguments.Length == 0) { + return new object[1]; + } + + var length = arguments.Length; + var args = new object[length + 1]; + Array.Copy(arguments, 0, args, 1, length); + return args; + } + } +} \ No newline at end of file diff --git a/src/LanguageServer/Impl/Definitions/ServerSettings.cs b/src/LanguageServer/Impl/Definitions/ServerSettings.cs index 88fc757a9..9d1319cb6 100644 --- a/src/LanguageServer/Impl/Definitions/ServerSettings.cs +++ b/src/LanguageServer/Impl/Definitions/ServerSettings.cs @@ -55,7 +55,9 @@ public void SetErrorSeverityOptions(string[] errors, string[] warnings, string[] public class PythonCompletionOptions { public bool showAdvancedMembers = true; + public bool addBrackets = false; } + public readonly PythonCompletionOptions completion = new PythonCompletionOptions(); } } diff --git a/src/LanguageServer/Impl/Implementation/CompletionAnalysis.cs b/src/LanguageServer/Impl/Implementation/CompletionAnalysis.cs index 589deb204..509f0c01b 100644 --- a/src/LanguageServer/Impl/Implementation/CompletionAnalysis.cs +++ b/src/LanguageServer/Impl/Implementation/CompletionAnalysis.cs @@ -180,7 +180,7 @@ public IEnumerable GetCompletionsFromString(string expr) { public IEnumerable GetCompletions() { var opts = Options | GetMemberOptions.ForEval; - bool allowKeywords = true, allowArguments = true; + bool allowKeywords = true; List additional = null; var res = GetNoCompletionsInComments() ?? @@ -191,10 +191,10 @@ public IEnumerable GetCompletions() { GetCompletionsInDefinition(ref allowKeywords, ref additional) ?? GetCompletionsInForStatement() ?? GetCompletionsInWithStatement() ?? - GetCompletionsInRaiseStatement(ref allowArguments, ref opts) ?? + GetCompletionsInRaiseStatement(ref opts) ?? GetCompletionsInExceptStatement(ref allowKeywords, ref opts) ?? GetCompletionsFromError() ?? - GetCompletionsFromTopLevel(allowKeywords, allowArguments, opts); + GetCompletionsFromTopLevel(allowKeywords, opts); if (additional != null) { res = res.Concat(additional); @@ -548,7 +548,7 @@ private IEnumerable GetCompletionsInWithStatement() { return null; } - private IEnumerable GetCompletionsInRaiseStatement(ref bool allowKeywords, ref GetMemberOptions opts) { + private IEnumerable GetCompletionsInRaiseStatement(ref GetMemberOptions opts) { if (Statement is RaiseStatement rs) { // raise Type, Value, Traceback with Cause if (rs.Cause != null) { @@ -751,7 +751,7 @@ private IEnumerable GetCompletionsFromError() { return null; } - private IEnumerable GetCompletionsFromTopLevel(bool allowKeywords, bool allowArguments, GetMemberOptions opts) { + private IEnumerable GetCompletionsFromTopLevel(bool allowKeywords, GetMemberOptions opts) { if (Node?.EndIndex < Index) { return Empty; } @@ -773,22 +773,20 @@ private IEnumerable GetCompletionsFromTopLevel(bool allowKeyword _log.TraceMessage($"Completing all names"); var members = Analysis.GetAllMembers(Position, opts); - if (allowArguments) { - var finder = new ExpressionFinder(Tree, new GetExpressionOptions { Calls = true }); - if (finder.GetExpression(Index) is CallExpression callExpr && - callExpr.GetArgumentAtIndex(Tree, Index, out _)) { - var argNames = Analysis.GetSignatures(callExpr.Target, Position) - .SelectMany(o => o.Parameters).Select(p => p?.Name) - .Where(n => !string.IsNullOrEmpty(n)) - .Distinct() - .Except(callExpr.Args.MaybeEnumerate().Select(a => a.Name).Where(n => !string.IsNullOrEmpty(n))) - .Select(n => new MemberResult($"{n}=", PythonMemberType.NamedArgument) as IMemberResult); + var finder = new ExpressionFinder(Tree, new GetExpressionOptions { Calls = true }); + if (finder.GetExpression(Index) is CallExpression callExpr && + callExpr.GetArgumentAtIndex(Tree, Index, out _)) { + var argNames = Analysis.GetSignatures(callExpr.Target, Position) + .SelectMany(o => o.Parameters).Select(p => p?.Name) + .Where(n => !string.IsNullOrEmpty(n)) + .Distinct() + .Except(callExpr.Args.MaybeEnumerate().Select(a => a.Name).Where(n => !string.IsNullOrEmpty(n))) + .Select(n => new MemberResult($"{n}=", PythonMemberType.NamedArgument) as IMemberResult); - argNames = argNames.MaybeEnumerate().ToArray(); - _log.TraceMessage($"Including {argNames.Count()} named arguments"); + argNames = argNames.MaybeEnumerate().ToArray(); + _log.TraceMessage($"Including {argNames.Count()} named arguments"); - members = members?.Concat(argNames) ?? argNames; - } + members = members?.Concat(argNames) ?? argNames; } return members.Select(ToCompletionItem).Where(c => !string.IsNullOrEmpty(c.insertText)); @@ -802,14 +800,7 @@ private IEnumerable GetCompletionsFromTopLevel(bool allowKeyword private static readonly CompletionItem ImportKeywordCompletion = ToCompletionItem("import", PythonMemberType.Keyword); private static readonly CompletionItem WithKeywordCompletion = ToCompletionItem("with", PythonMemberType.Keyword); private static readonly CompletionItem StarCompletion = ToCompletionItem("*", PythonMemberType.Keyword); - - private static CompletionItem KeywordCompletion(string keyword) => new CompletionItem { - label = keyword, - insertText = keyword, - kind = CompletionItemKind.Keyword, - _kind = PythonMemberType.Keyword.ToString().ToLowerInvariant() - }; - + private CompletionItem ToCompletionItem(IMemberResult m) { var completion = m.Completion; if (string.IsNullOrEmpty(completion)) { diff --git a/src/LanguageServer/Impl/Implementation/Server.Completion.cs b/src/LanguageServer/Impl/Implementation/Server.Completion.cs index f17856b37..e3776cb59 100644 --- a/src/LanguageServer/Impl/Implementation/Server.Completion.cs +++ b/src/LanguageServer/Impl/Implementation/Server.Completion.cs @@ -61,8 +61,21 @@ public override async Task Completion(CompletionParams @params, members = members.Where(m => m.kind == filterKind.Value); } + var completions = members.ToArray(); + if (Settings.completion.addBrackets && (!filterKind.HasValue || CanHaveBrackets(filterKind.Value))) { + foreach (var completionItem in completions.Where(ci => CanHaveBrackets(ci.kind))) { + completionItem.insertText += "($0)"; + completionItem.insertTextFormat = InsertTextFormat.Snippet; + } + } + + bool CanHaveBrackets(CompletionItemKind kind) + => kind == CompletionItemKind.Constructor || + kind == CompletionItemKind.Function || + kind == CompletionItemKind.Method; + var res = new CompletionList { - items = members.ToArray(), + items = completions, _expr = ctxt.ParentExpression?.ToCodeString(tree, CodeFormattingOptions.Traditional), _commitByDefault = ctxt.ShouldCommitByDefault, _allowSnippet = ctxt.ShouldAllowSnippets diff --git a/src/LanguageServer/Impl/LanguageServer.cs b/src/LanguageServer/Impl/LanguageServer.cs index 425475bba..d77d055d0 100644 --- a/src/LanguageServer/Impl/LanguageServer.cs +++ b/src/LanguageServer/Impl/LanguageServer.cs @@ -138,6 +138,7 @@ public async Task DidChangeConfiguration(JToken token, CancellationToken cancell var autoComplete = pythonSection["autoComplete"]; settings.completion.showAdvancedMembers = GetSetting(autoComplete, "showAdvancedMembers", true); + settings.completion.addBrackets = GetSetting(autoComplete, "addBrackets", false); var analysis = pythonSection["analysis"]; settings.analysis.openFilesOnly = GetSetting(analysis, "openFilesOnly", false); @@ -175,7 +176,7 @@ public async Task DidChangeWatchedFiles(JToken token, CancellationToken cancella [JsonRpcMethod("workspace/symbol")] public async Task WorkspaceSymbols(JToken token, CancellationToken cancellationToken) { - await _prioritizer.DefaultPriorityAsync(); + await _prioritizer.DefaultPriorityAsync(cancellationToken); return await _server.WorkspaceSymbols(ToObject(token), cancellationToken); } diff --git a/src/UnitTests/Core/Impl/TestEnvironmentImpl.cs b/src/UnitTests/Core/Impl/TestEnvironmentImpl.cs index f9604994e..844c8659e 100644 --- a/src/UnitTests/Core/Impl/TestEnvironmentImpl.cs +++ b/src/UnitTests/Core/Impl/TestEnvironmentImpl.cs @@ -15,6 +15,7 @@ // permissions and limitations under the License. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; @@ -27,7 +28,11 @@ public class TestEnvironmentImpl { public static TimeSpan Elapsed() => Instance?._stopwatch.Value?.Elapsed ?? new TimeSpan(); public static void TestInitialize(string testFullName, int secondsTimeout = 10) => Instance?.BeforeTestRun(testFullName, secondsTimeout); public static void TestCleanup() => Instance?.AfterTestRun(); + public static void AddBeforeAfterTest(Func> beforeAfterTest) => Instance?.AddBeforeAfterTestAction(() => beforeAfterTest().GetAwaiter().GetResult()); + public static void AddBeforeAfterTest(Func beforeAfterTest) => Instance?.AddBeforeAfterTestAction(beforeAfterTest); + private readonly AsyncLocal>> _beforeAfterTestActions = new AsyncLocal>>(); + private readonly AsyncLocal> _beforeAfterTestActionDisposables = new AsyncLocal>(); private readonly AsyncLocal _taskObserver = new AsyncLocal(); private readonly AsyncLocal _stopwatch = new AsyncLocal(); private readonly AssemblyLoader _assemblyLoader = new AssemblyLoader(); @@ -55,14 +60,40 @@ protected virtual void BeforeTestRun(string testFullName, int secondsTimeout) { throw new InvalidOperationException("AsyncLocal reentrancy"); } + var beforeAfterTestActions = _beforeAfterTestActions.Value; + _beforeAfterTestActions.Value = null; + _taskObserver.Value = new TaskObserver(secondsTimeout); _stopwatch.Value = new Stopwatch(); _stopwatch.Value.Start(); TestData.SetTestRunScope(testFullName); + + if (beforeAfterTestActions != null) { + var disposables = new Stack(); + try { + foreach (var beforeAfterTestAction in beforeAfterTestActions) { + disposables.Push(beforeAfterTestAction()); + } + } catch (Exception) { + RunDisposablesSafe(disposables); + throw; + } + + _beforeAfterTestActionDisposables.Value = disposables; + } } protected virtual void AfterTestRun() { try { + var disposables = _beforeAfterTestActionDisposables.Value; + _beforeAfterTestActionDisposables.Value = null; + if (disposables != null) { + var afterTestRunException = RunDisposablesSafe(disposables); + if (afterTestRunException != null) { + throw afterTestRunException; + } + } + _taskObserver.Value?.WaitForObservedTask(); _stopwatch.Value?.Stop(); TestData.ClearTestRunScope(); @@ -71,5 +102,24 @@ protected virtual void AfterTestRun() { _taskObserver.Value = null; } } + + private void AddBeforeAfterTestAction(Func beforeAfterTest) { + var actions = _beforeAfterTestActions.Value ?? (_beforeAfterTestActions.Value = new List>()); + actions.Add(beforeAfterTest); + } + + private AggregateException RunDisposablesSafe(Stack disposables) { + var exceptions = new List(); + while (disposables.Count > 0) { + var disposable = disposables.Pop(); + try { + disposable.Dispose(); + } catch (Exception ex) { + exceptions.Add(ex); + } + } + + return exceptions.Count > 0 ? new AggregateException(exceptions) : null; + } } } \ No newline at end of file