diff --git a/src/Analysis/Engine/Impl/Analyzer/DDG.cs b/src/Analysis/Engine/Impl/Analyzer/DDG.cs index ed840aa19..587965069 100644 --- a/src/Analysis/Engine/Impl/Analyzer/DDG.cs +++ b/src/Analysis/Engine/Impl/Analyzer/DDG.cs @@ -441,14 +441,14 @@ private bool TryImportModule(string modName, bool forceAbsolute, out ModuleRefer if (gotAllParents && ProjectState.Modules.TryImport(name, out modRef)) { moduleRef = modRef; (lastParent as BuiltinModule)?.AddChildModule(remainingParts[0], moduleRef.AnalysisModule); - _unit.DeclaringModule.AddModuleReference(moduleRef); + _unit.DeclaringModule.AddModuleReference(modName, moduleRef); remainingParts = null; return true; } } if (moduleRef?.Module != null) { - _unit.DeclaringModule.AddModuleReference(moduleRef); + _unit.DeclaringModule.AddModuleReference(modName, moduleRef); return true; } return false; diff --git a/src/Analysis/Engine/Impl/Infrastructure/Check.cs b/src/Analysis/Engine/Impl/Infrastructure/Check.cs index 9741acfc0..694296234 100644 --- a/src/Analysis/Engine/Impl/Infrastructure/Check.cs +++ b/src/Analysis/Engine/Impl/Infrastructure/Check.cs @@ -17,7 +17,7 @@ public static void FieldType(string fieldName, object fieldValue) { [DebuggerStepThrough] public static void ArgumentOfType(string argumentName, object argument, [CallerMemberName] string callerName = null) { - ArgumentNull(argumentName, argument); + ArgumentNotNull(argumentName, argument); if (!(argument is T)) { throw new ArgumentException($"Argument {argumentName} of method {callerName} must be of type {typeof(T)}"); @@ -25,7 +25,7 @@ public static void ArgumentOfType(string argumentName, object argument, [Call } [DebuggerStepThrough] - public static void ArgumentNull(string argumentName, object argument) { + public static void ArgumentNotNull(string argumentName, object argument) { if (argument is null) { throw new ArgumentNullException(argumentName); } @@ -33,7 +33,7 @@ public static void ArgumentNull(string argumentName, object argument) { [DebuggerStepThrough] public static void ArgumentNotNullOrEmpty(string argumentName, string argument) { - ArgumentNull(argumentName, argument); + ArgumentNotNull(argumentName, argument); if (string.IsNullOrEmpty(argument)) { throw new ArgumentException(argumentName); diff --git a/src/Analysis/Engine/Impl/Infrastructure/Disposable.cs b/src/Analysis/Engine/Impl/Infrastructure/Disposable.cs index 18d97615b..09070f159 100644 --- a/src/Analysis/Engine/Impl/Infrastructure/Disposable.cs +++ b/src/Analysis/Engine/Impl/Infrastructure/Disposable.cs @@ -30,7 +30,7 @@ public static class Disposable { /// The disposable object that runs the given action upon disposal. /// is null. public static IDisposable Create(Action dispose) { - Check.ArgumentNull(nameof(dispose), dispose); + Check.ArgumentNotNull(nameof(dispose), dispose); return new AnonymousDisposable(dispose); } @@ -41,7 +41,7 @@ public static IDisposable Create(Action dispose) { /// The disposable object that disposes wrapped object. /// is null. public static IDisposable Create(IDisposable disposable) { - Check.ArgumentNull(nameof(disposable), disposable); + Check.ArgumentNotNull(nameof(disposable), disposable); return new DisposableWrapper(disposable); } diff --git a/src/Analysis/Engine/Impl/MemberResult.cs b/src/Analysis/Engine/Impl/MemberResult.cs index 622f090de..89bcad1f5 100644 --- a/src/Analysis/Engine/Impl/MemberResult.cs +++ b/src/Analysis/Engine/Impl/MemberResult.cs @@ -160,15 +160,17 @@ private PythonMemberType GetMemberType() { return result; } - public override bool Equals(object obj) { - if (!(obj is MemberResult)) { - return false; + public bool Equals(MemberResult other) => string.Equals(Name, other.Name) && Equals(Scope, other.Scope); + + public override bool Equals(object obj) => obj is MemberResult other && Equals(other); + + public override int GetHashCode() { + unchecked { + return ((Name != null ? Name.GetHashCode() : 0) * 397) ^ (Scope != null ? Scope.GetHashCode() : 0); } - return Name == ((MemberResult)obj).Name; } - public static bool operator ==(MemberResult x, MemberResult y) => x.Name == y.Name; - public static bool operator !=(MemberResult x, MemberResult y) => x.Name != y.Name; - public override int GetHashCode() => Name.GetHashCode(); + public static bool operator ==(MemberResult left, MemberResult right) => left.Equals(right); + public static bool operator !=(MemberResult left, MemberResult right) => !left.Equals(right); } } diff --git a/src/Analysis/Engine/Impl/Parsing/Ast/DottedName.cs b/src/Analysis/Engine/Impl/Parsing/Ast/DottedName.cs index 0c96e7656..afd0af2a4 100644 --- a/src/Analysis/Engine/Impl/Parsing/Ast/DottedName.cs +++ b/src/Analysis/Engine/Impl/Parsing/Ast/DottedName.cs @@ -22,16 +22,14 @@ namespace Microsoft.PythonTools.Parsing.Ast { public class DottedName : Node { private readonly NameExpression[] _names; - public DottedName(NameExpression[] names) { + public DottedName(NameExpression[]/*!*/ names) { _names = names; } - public IList Names { - get { return _names; } - } + public IList Names => _names; public virtual string MakeString() { - if (_names.Length == 0) return String.Empty; + if (_names.Length == 0) return string.Empty; StringBuilder ret = new StringBuilder(_names[0].Name); for (int i = 1; i < _names.Length; i++) { diff --git a/src/Analysis/Engine/Impl/Parsing/Ast/FromImportStatement.cs b/src/Analysis/Engine/Impl/Parsing/Ast/FromImportStatement.cs index 30a9579ff..4b597aa62 100644 --- a/src/Analysis/Engine/Impl/Parsing/Ast/FromImportStatement.cs +++ b/src/Analysis/Engine/Impl/Parsing/Ast/FromImportStatement.cs @@ -25,7 +25,7 @@ public class FromImportStatement : Statement { private static readonly string[] _star = new[] { "*" }; private PythonVariable[] _variables; - public FromImportStatement(ModuleName root, NameExpression/*!*/[] names, NameExpression[] asNames, bool fromFuture, bool forceAbsolute, int importIndex) { + public FromImportStatement(ModuleName/*!*/ root, NameExpression/*!*/[] names, NameExpression[] asNames, bool fromFuture, bool forceAbsolute, int importIndex) { Root = root; Names = names; AsNames = asNames; diff --git a/src/Analysis/Engine/Impl/Parsing/Ast/ModuleName.cs b/src/Analysis/Engine/Impl/Parsing/Ast/ModuleName.cs index 18652bbaa..01bb749f2 100644 --- a/src/Analysis/Engine/Impl/Parsing/Ast/ModuleName.cs +++ b/src/Analysis/Engine/Impl/Parsing/Ast/ModuleName.cs @@ -16,7 +16,7 @@ namespace Microsoft.PythonTools.Parsing.Ast { public class ModuleName : DottedName { - public ModuleName(NameExpression[] names) + public ModuleName(NameExpression[]/*!*/ names) : base(names) { } } diff --git a/src/Analysis/Engine/Impl/Parsing/Ast/RelativeModuleName.cs b/src/Analysis/Engine/Impl/Parsing/Ast/RelativeModuleName.cs index 90840e7a5..ef0501463 100644 --- a/src/Analysis/Engine/Impl/Parsing/Ast/RelativeModuleName.cs +++ b/src/Analysis/Engine/Impl/Parsing/Ast/RelativeModuleName.cs @@ -18,26 +18,18 @@ namespace Microsoft.PythonTools.Parsing.Ast { public class RelativeModuleName : ModuleName { - private readonly int _dotCount; - - public RelativeModuleName(NameExpression[] names, int dotCount) + public RelativeModuleName(NameExpression[]/*!*/ names, int dotCount) : base(names) { - _dotCount = dotCount; + DotCount = dotCount; } - public override string MakeString() { - return new string('.', DotCount) + base.MakeString(); - } + public override string MakeString() => new string('.', DotCount) + base.MakeString(); - public int DotCount { - get { - return _dotCount; - } - } + public int DotCount { get; } internal override void AppendCodeString(StringBuilder res, PythonAst ast, CodeFormattingOptions format) { var whitespace = this.GetListWhiteSpace(ast); - for (int i = 0; i < _dotCount; i++) { + for (int i = 0; i < DotCount; i++) { if (whitespace != null) { res.Append(whitespace[i]); } diff --git a/src/Analysis/Engine/Impl/ProjectEntry.cs b/src/Analysis/Engine/Impl/ProjectEntry.cs index 45e0f2ac0..8728bad79 100644 --- a/src/Analysis/Engine/Impl/ProjectEntry.cs +++ b/src/Analysis/Engine/Impl/ProjectEntry.cs @@ -268,6 +268,7 @@ private void Parse(bool enqueueOnly, CancellationToken cancel) { MyScope.Scope.ClearNodeValues(); MyScope.Scope.ClearLinkedVariables(); MyScope.Scope.ClearVariables(); + MyScope.ClearReferencedModules(); MyScope.ClearUnresolvedModules(); _unit.State.ClearDiagnostics(this); @@ -366,10 +367,7 @@ public void Dispose() { } } - foreach (var moduleReference in MyScope.ModuleReferences.ToList()) { - MyScope.RemoveModuleReference(moduleReference); - } - + MyScope.ClearReferencedModules(); NewParseTree -= NewParseTree; NewAnalysis -= NewAnalysis; } diff --git a/src/Analysis/Engine/Impl/Values/ModuleInfo.cs b/src/Analysis/Engine/Impl/Values/ModuleInfo.cs index b9683b252..1ba82880f 100644 --- a/src/Analysis/Engine/Impl/Values/ModuleInfo.cs +++ b/src/Analysis/Engine/Impl/Values/ModuleInfo.cs @@ -9,7 +9,7 @@ // 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, -// MERCHANTABLITY OR NON-INFRINGEMENT. +// MERCHANTABILITY OR NON-INFRINGEMENT. // // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. @@ -20,44 +20,39 @@ using System.Linq; using System.Text; using Microsoft.PythonTools.Analysis.Analyzer; +using Microsoft.PythonTools.Analysis.Infrastructure; using Microsoft.PythonTools.Interpreter; using Microsoft.PythonTools.Parsing; using Microsoft.PythonTools.Parsing.Ast; namespace Microsoft.PythonTools.Analysis.Values { internal class ModuleInfo : AnalysisValue, IReferenceableContainer, IModuleInfo { - private readonly string _name; private readonly ProjectEntry _projectEntry; - private Dictionary _sequences; // sequences defined in the module private readonly Dictionary _scopes; // scopes from Ast node to InterpreterScope - private readonly WeakReference _weakModule; - private readonly IModuleContext _context; private Dictionary _packageModules; private Dictionary> _specialized; - private ModuleInfo _parentPackage; - private DependentData _definition = new DependentData(); - private readonly HashSet _referencedModules; - private readonly HashSet _unresolvedModules; + private readonly Dictionary _referencedModules; + private readonly HashSet _unresolvedModules; public ModuleInfo(string moduleName, ProjectEntry projectEntry, IModuleContext moduleContext) { - _name = moduleName; + Name = moduleName; _projectEntry = projectEntry; - _sequences = new Dictionary(); Scope = new ModuleScope(this); - _weakModule = new WeakReference(this); - _context = moduleContext; + WeakModule = new WeakReference(this); + InterpreterContext = moduleContext; _scopes = new Dictionary(); - _referencedModules = new HashSet(); + _referencedModules = new Dictionary(); _unresolvedModules = new HashSet(StringComparer.Ordinal); } internal void Clear() { - _sequences.Clear(); - Scope.ClearLinkedVariables(); - Scope.ClearVariables(); - Scope.ClearNodeScopes(); - _referencedModules.Clear(); - _unresolvedModules.Clear(); + lock (_projectEntry) { + Scope.ClearLinkedVariables(); + Scope.ClearVariables(); + Scope.ClearNodeScopes(); + _unresolvedModules.Clear(); + ClearReferencedModules(); + } } internal void EnsureModuleVariables(PythonAnalyzer state) { @@ -87,17 +82,15 @@ private static IAnalysisSet GetStr(PythonAnalyzer state, string s = null) { return state.GetConstant(s); } + /// /// /// Returns all the absolute module names that need to be resolved from /// this module. - /// /// Note that a single import statement may add multiple names to this /// set, and so the Count property does not accurately reflect the /// actual number of imports required. /// - public ISet GetAllUnresolvedModules() { - return _unresolvedModules; - } + public ISet GetAllUnresolvedModules() => _unresolvedModules; internal void AddUnresolvedModule(string relativeModuleName, bool absoluteImports) { _unresolvedModules.UnionWith(ModuleResolver.ResolvePotentialModuleNames(_projectEntry, relativeModuleName, absoluteImports)); @@ -115,6 +108,7 @@ public override IDictionary GetAllMembers(IModuleContext m if (!options.ForEval()) { kvp.Value.ClearOldValues(); } + if (kvp.Value._dependencies.Count > 0) { var types = kvp.Value.Types; if (types.Count > 0) { @@ -125,12 +119,9 @@ public override IDictionary GetAllMembers(IModuleContext m return res; } - public IModuleContext InterpreterContext => _context; + public IModuleContext InterpreterContext { get; } - public ModuleInfo ParentPackage { - get { return _parentPackage; } - set { _parentPackage = value; } - } + public ModuleInfo ParentPackage { get; set; } public void AddChildPackage(ModuleInfo childPackage, AnalysisUnit curUnit, string realName = null) { realName = realName ?? childPackage.Name; @@ -151,8 +142,7 @@ public void AddChildPackage(ModuleInfo childPackage, AnalysisUnit curUnit, strin public IEnumerable> GetChildrenPackages(IModuleContext moduleContext) { if (_packageModules != null) { foreach (var keyValue in _packageModules) { - var res = keyValue.Value.Target as IModule; - if (res != null) { + if (keyValue.Value.Target is IModule res) { yield return new KeyValuePair(keyValue.Key, (AnalysisValue)res); } } @@ -160,8 +150,7 @@ public IEnumerable> GetChildrenPackages(IMod } public IModule GetChildPackage(IModuleContext moduleContext, string name) { - WeakReference weakMod; - if (_packageModules != null && _packageModules.TryGetValue(name, out weakMod)) { + if (_packageModules != null && _packageModules.TryGetValue(name, out var weakMod)) { var res = weakMod.Target; if (res != null) { return (IModule)res; @@ -172,22 +161,27 @@ public IModule GetChildPackage(IModuleContext moduleContext, string name) { return null; } - public void AddModuleReference(ModuleReference moduleRef) { - if (moduleRef == null) { - Debug.Fail("moduleRef should never be null"); - throw new ArgumentNullException(nameof(moduleRef)); + public bool TryGetModuleReference(string moduleName, out ModuleReference moduleReference) { + lock (_projectEntry) { + return _referencedModules.TryGetValue(moduleName, out moduleReference); } - _referencedModules.Add(moduleRef); - moduleRef.AddReference(this); } - public void RemoveModuleReference(ModuleReference moduleRef) { - if (_referencedModules.Remove(moduleRef)) { - moduleRef.RemoveReference(this); + public void AddModuleReference(string moduleName, ModuleReference moduleReference) { + Check.ArgumentNotNull(nameof(moduleName), moduleName); + Check.ArgumentNotNull(nameof(moduleReference), moduleReference); + + _referencedModules[moduleName] = moduleReference; + moduleReference.AddReference(this); + } + + public void ClearReferencedModules() { + foreach (var moduleReference in _referencedModules.Values.Distinct()) { + moduleReference.RemoveReference(this); } + _referencedModules.Clear(); } - public IEnumerable ModuleReferences => _referencedModules; public void SpecializeFunction(string name, CallDelegate callable, bool mergeOriginalAnalysis) { lock (this) { if (_specialized == null) { @@ -209,19 +203,14 @@ internal void Specialize() { private void SpecializeOneFunction(string name, CallDelegate callable, bool mergeOriginalAnalysis) { int lastIndex; - VariableDef def; - if (Scope.TryGetVariable(name, out def)) { + if (Scope.TryGetVariable(name, out var def)) { SpecializeVariableDef(def, callable, mergeOriginalAnalysis); } else if ((lastIndex = name.LastIndexOf('.')) != -1 && Scope.TryGetVariable(name.Substring(0, lastIndex), out def)) { var methodName = name.Substring(lastIndex + 1, name.Length - (lastIndex + 1)); foreach (var v in def.Types) { - var ci = v as ClassInfo; - if (ci != null) { - VariableDef methodDef; - if (ci.Scope.TryGetVariable(methodName, out methodDef)) { - SpecializeVariableDef(methodDef, callable, mergeOriginalAnalysis); - } + if (v is ClassInfo ci && ci.Scope.TryGetVariable(methodName, out var methodDef)) { + SpecializeVariableDef(methodDef, callable, mergeOriginalAnalysis); } } } @@ -247,12 +236,11 @@ public override IAnalysisSet GetTypeMember(Node node, AnalysisUnit unit, string public override IAnalysisSet GetMember(Node node, AnalysisUnit unit, string name) { if (unit.ForEval) { - VariableDef value; - return Scope.TryGetVariable(name, out value) ? value.Types : AnalysisSet.Empty; - } else { - ModuleDefinition.AddDependency(unit); - return Scope.CreateEphemeralVariable(node, unit, name).Types; + return Scope.TryGetVariable(name, out var value) ? value.Types : AnalysisSet.Empty; } + + ModuleDefinition.AddDependency(unit); + return Scope.CreateEphemeralVariable(node, unit, name).Types; } public override void SetMember(Node node, AnalysisUnit unit, string name, IAnalysisSet value) { @@ -267,14 +255,14 @@ public override void SetMember(Node node, AnalysisUnit unit, string name, IAnaly /// /// Gets a weak reference to this module /// - public WeakReference WeakModule => _weakModule; + public WeakReference WeakModule { get; } - public DependentData ModuleDefinition => _definition; + public DependentData ModuleDefinition { get; } = new DependentData(); - public ModuleScope Scope { get; private set; } + public ModuleScope Scope { get; } IScope IModule.Scope => Scope; - public override string Name => _name; + public override string Name { get; } public ProjectEntry ProjectEntry => _projectEntry; IPythonProjectEntry IModule.ProjectEntry => ProjectEntry; @@ -297,14 +285,7 @@ public override string Description { } } - public override string Documentation { - get { - if (ProjectEntry.Tree != null && ProjectEntry.Tree.Body != null) { - return ProjectEntry.Tree.Body.Documentation.TrimDocumentation() ?? String.Empty; - } - return String.Empty; - } - } + public override string Documentation => ProjectEntry.Tree?.Body?.Documentation.TrimDocumentation() ?? string.Empty; public override IEnumerable Locations => new[] { new LocationInfo(ProjectEntry.FilePath, ProjectEntry.DocumentUri, 1, 1) }; @@ -317,8 +298,7 @@ public override IPythonType PythonType #region IVariableDefContainer Members public IEnumerable GetDefinitions(string name) { - VariableDef def; - if (Scope.TryGetVariable(name, out def)) { + if (Scope.TryGetVariable(name, out var def)) { yield return def; } } @@ -332,13 +312,10 @@ public IAnalysisSet GetModuleMember(Node node, AnalysisUnit unit, string name, b var importedValue = Scope.CreateEphemeralVariable(node, unit, name, addRef); ModuleDefinition.AddDependency(unit); - if (linkedScope != null) { - linkedScope.AddLinkedVariable(linkedName ?? name, importedValue); - } + linkedScope?.AddLinkedVariable(linkedName ?? name, importedValue); return importedValue.GetTypesNoCopy(unit, DeclaringModule); } - public IEnumerable GetModuleMemberNames(IModuleContext context) { return Scope.AllVariables.Select(kv => kv.Key); } diff --git a/src/Analysis/Engine/Test/AnalysisTest.cs b/src/Analysis/Engine/Test/AnalysisTest.cs index 322f5ca29..6e3f89bc9 100644 --- a/src/Analysis/Engine/Test/AnalysisTest.cs +++ b/src/Analysis/Engine/Test/AnalysisTest.cs @@ -4182,8 +4182,7 @@ public async Task PackageRelativeImportAliasedMember() { analysisPackage.Should().HaveVariable("y").OfTypes(BuiltinTypeId.Module, BuiltinTypeId.Function); } } - - + [TestMethod, Priority(0)] public async Task Defaults() { var text = @" diff --git a/src/Analysis/Engine/Test/CompletionTest.cs b/src/Analysis/Engine/Test/CompletionTest.cs index 9d05c0488..b0aa7556e 100644 --- a/src/Analysis/Engine/Test/CompletionTest.cs +++ b/src/Analysis/Engine/Test/CompletionTest.cs @@ -15,6 +15,8 @@ // permissions and limitations under the License. using System; +using System.IO; +using System.Threading; using System.Threading.Tasks; using Microsoft.Python.LanguageServer; using Microsoft.Python.LanguageServer.Implementation; @@ -432,5 +434,44 @@ class B(A): completion.Should().HaveItem("foo") .Which.Should().HaveInsertText(expectedInsertText); } + + [ServerTestMethod(TestSpecificRootUri = true), Priority(0)] + public async Task Completion_PackageRelativeImport(Server server) { + var appPath = "app.py"; + var module1Path = "module1.py"; + var packageInitPath = Path.Combine("package", "__init__.py"); + var packageModule1Path = Path.Combine("package", "module1.py"); + var subpackageInitPath = Path.Combine("package", "sub_package", "__init__.py"); + var subpackageTestingPath = Path.Combine("package", "sub_package", "testing.py"); + + var appSrc = "import package.sub_package.testing"; + var module1Src = "def wrong(): print('WRONG'); pass"; + var packageInitSrc = string.Empty; + var packageModule1Src = "def right(): print('RIGHT'); pass"; + var subpackageInitSrc = string.Empty; + var subpackageTestingSrc = "from ..module1 import "; + + await TestData.CreateTestSpecificFileAsync(appPath, appSrc); + await TestData.CreateTestSpecificFileAsync(module1Path, module1Src); + await TestData.CreateTestSpecificFileAsync(packageInitPath, packageInitSrc); + await TestData.CreateTestSpecificFileAsync(packageModule1Path, packageModule1Src); + await TestData.CreateTestSpecificFileAsync(subpackageInitPath, subpackageInitSrc); + var uri = await TestData.CreateTestSpecificFileAsync(subpackageTestingPath, subpackageTestingSrc); + + using (server.AnalysisQueue.Pause()) { + await server.OpenDocumentAndGetUriAsync(appPath, appSrc); + await server.OpenDocumentAndGetUriAsync(module1Path, module1Src); + await server.OpenDocumentAndGetUriAsync(packageInitPath, packageInitSrc); + await server.OpenDocumentAndGetUriAsync(packageModule1Path, packageModule1Src); + await server.OpenDocumentAndGetUriAsync(subpackageInitPath, subpackageInitSrc); + await server.OpenDocumentAndGetUriAsync(subpackageTestingPath, subpackageTestingSrc); + } + + await server.WaitForCompleteAnalysisAsync(CancellationToken.None); + var completion = await server.SendCompletion(uri, 0, 22); + + completion.Should().HaveItem("right") + .Which.Should().HaveInsertText("right"); + } } } \ No newline at end of file diff --git a/src/Analysis/Engine/Test/LineFormatterTests.cs b/src/Analysis/Engine/Test/LineFormatterTests.cs index 3a2a133ce..674b7e54f 100644 --- a/src/Analysis/Engine/Test/LineFormatterTests.cs +++ b/src/Analysis/Engine/Test/LineFormatterTests.cs @@ -453,8 +453,8 @@ public void GrammarFile() { /// Python language version to format. /// Where the edit should begin (i.e. when whitespace or a multi-line string begins a line). public static void AssertSingleLineFormat(string text, string expected, int line = 0, PythonLanguageVersion languageVersion = PythonLanguageVersion.V37, int editStart = 0) { - Check.ArgumentNull(nameof(text), text); - Check.ArgumentNull(nameof(expected), expected); + Check.ArgumentNotNull(nameof(text), text); + Check.ArgumentNotNull(nameof(expected), expected); using (var reader = new StringReader(text)) { var edits = new LineFormatter(reader, languageVersion).FormatLine(line); @@ -463,7 +463,7 @@ public static void AssertSingleLineFormat(string text, string expected, int line } public static void AssertNoEdits(string text, int line = 0, PythonLanguageVersion languageVersion = PythonLanguageVersion.V37) { - Check.ArgumentNull(nameof(text), text); + Check.ArgumentNotNull(nameof(text), text); using (var reader = new StringReader(text)) { var edits = new LineFormatter(reader, languageVersion).FormatLine(line); diff --git a/src/Analysis/Engine/Test/ServerExtensions.cs b/src/Analysis/Engine/Test/ServerExtensions.cs index cfc562917..5ecd3b1af 100644 --- a/src/Analysis/Engine/Test/ServerExtensions.cs +++ b/src/Analysis/Engine/Test/ServerExtensions.cs @@ -62,7 +62,7 @@ await server.Initialize(new InitializeParams { } public static Task GetAnalysisAsync(this Server server, Uri uri, int waitingTimeout = -1, int failAfter = 30000) - => ((ProjectEntry)server.ProjectFiles.GetEntry(uri)).GetAnalysisAsync(waitingTimeout, new CancellationTokenSource(failAfter).Token); + => ((ProjectEntry)server.ProjectFiles.GetEntry(uri)).GetAnalysisAsync(waitingTimeout, GetCancellationToken(failAfter)); public static Task EnqueueItemAsync(this Server server, Uri uri) => server.EnqueueItemAsync((IDocument)server.ProjectFiles.GetEntry(uri)); @@ -158,7 +158,7 @@ await server.DidOpenTextDocument(new DidOpenTextDocumentParams { } public static async Task OpenDefaultDocumentAndGetAnalysisAsync(this Server server, string content, int failAfter = 30000, string languageId = null) { - var cancellationToken = new CancellationTokenSource(failAfter).Token; + var cancellationToken = GetCancellationToken(failAfter); await server.SendDidOpenTextDocument(TestData.GetDefaultModuleUri(), content, languageId); cancellationToken.ThrowIfCancellationRequested(); var projectEntry = (ProjectEntry) server.ProjectFiles.Single(); @@ -209,12 +209,12 @@ public static Task SendDidChangeConfiguration(this Server server, ServerSettings } public static Task SendDidChangeConfiguration(this Server server, ServerSettings settings, int failAfter = 30000) - => server.DidChangeConfiguration(new DidChangeConfigurationParams { settings = settings }, new CancellationTokenSource(failAfter).Token); + => server.DidChangeConfiguration(new DidChangeConfigurationParams { settings = settings }, GetCancellationToken(failAfter)); 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); - return await projectEntry.GetAnalysisAsync(cancellationToken: new CancellationTokenSource(failAfter).Token); + return await projectEntry.GetAnalysisAsync(cancellationToken: GetCancellationToken(failAfter)); } public static string[] GetBuiltinTypeMemberNames(this Server server, BuiltinTypeId typeId) @@ -228,5 +228,8 @@ private static void Server_OnLogMessage(object sender, LogMessageEventArgs e) { case MessageType.Log: Trace.TraceInformation($"[{TestEnvironmentImpl.Elapsed()}] LOG: {e.message}"); break; } } + + private static CancellationToken GetCancellationToken(int failAfter = 30000) + => Debugger.IsAttached ? CancellationToken.None : new CancellationTokenSource(failAfter).Token; } } diff --git a/src/Analysis/Engine/Test/ServerTestMethodAttribute.cs b/src/Analysis/Engine/Test/ServerTestMethodAttribute.cs index d17f861e1..89bcd6f23 100644 --- a/src/Analysis/Engine/Test/ServerTestMethodAttribute.cs +++ b/src/Analysis/Engine/Test/ServerTestMethodAttribute.cs @@ -27,6 +27,7 @@ namespace AnalysisTests { public enum PythonLanguageMajorVersion { None, EarliestV2, EarliestV3, LatestV2, LatestV3 } public class ServerTestMethodAttribute : TestMethodAttribute { + public bool TestSpecificRootUri { get; set; } public bool LatestAvailable3X { get; set; } public bool LatestAvailable2X { get; set; } public int VersionArgumentIndex { get; set; } = -1; @@ -44,7 +45,8 @@ private TestResult ExecuteWithServer(ITestMethod testMethod) { TestEnvironmentImpl.AddBeforeAfterTest(async () => { var interpreterConfiguration = GetInterpreterConfiguration(arguments); - var server = await new Server().InitializeAsync(interpreterConfiguration); + var rootUri = TestSpecificRootUri ? TestData.GetTestSpecificRootUri() : null; + var server = await new Server().InitializeAsync(interpreterConfiguration, rootUri); arguments[0] = server; return server; }); diff --git a/src/LanguageServer/Impl/Implementation/BlockFormatter.cs b/src/LanguageServer/Impl/Implementation/BlockFormatter.cs index b03cfeda3..c27e24652 100644 --- a/src/LanguageServer/Impl/Implementation/BlockFormatter.cs +++ b/src/LanguageServer/Impl/Implementation/BlockFormatter.cs @@ -52,7 +52,7 @@ internal class BlockFormatter { }; public static async Task ProvideEdits(TextReader reader, Position position, FormattingOptions options) { - Check.ArgumentNull(nameof(reader), reader); + Check.ArgumentNotNull(nameof(reader), reader); Check.ArgumentOutOfRange(nameof(position), () => position.line < 0); if (position.line == 0) { @@ -92,7 +92,7 @@ public static async Task ProvideEdits(TextReader reader, Position po private readonly IEnumerable _previousBlockRegexps; private BlockFormatter(Regex blockRegexp, IEnumerable previousBlockRegexps) { - Check.ArgumentNull(nameof(blockRegexp), blockRegexp); + Check.ArgumentNotNull(nameof(blockRegexp), blockRegexp); Check.Argument(nameof(previousBlockRegexps), () => !previousBlockRegexps.IsNullOrEmpty()); _blockRegexp = blockRegexp; diff --git a/src/LanguageServer/Impl/Implementation/CompletionAnalysis.cs b/src/LanguageServer/Impl/Implementation/CompletionAnalysis.cs index b293f635e..699d3126a 100644 --- a/src/LanguageServer/Impl/Implementation/CompletionAnalysis.cs +++ b/src/LanguageServer/Impl/Implementation/CompletionAnalysis.cs @@ -24,6 +24,7 @@ using Microsoft.PythonTools.Analysis; using Microsoft.PythonTools.Analysis.Documentation; using Microsoft.PythonTools.Analysis.Infrastructure; +using Microsoft.PythonTools.Analysis.Values; using Microsoft.PythonTools.Interpreter; using Microsoft.PythonTools.Parsing; using Microsoft.PythonTools.Parsing.Ast; @@ -261,27 +262,19 @@ private IEnumerable GetCompletionsFromMembers(MemberExpression m private IEnumerable GetModules(string[] names, bool includeMembers) { if (names.Any()) { return Analysis.ProjectState - .GetModuleMembers(Analysis.InterpreterContext, names.ToArray(), includeMembers) + .GetModuleMembers(Analysis.InterpreterContext, names, includeMembers) .Select(ToCompletionItem); } - return Analysis.ProjectState.GetModules().Select(ToCompletionItem); + return GetModules(); } - private IEnumerable GetModulesFromNode(Node name, bool includeMembers = false) { - switch (name) { - case DottedName dn when dn.Names == null: - return Empty; - case DottedName dn: - var names = dn.Names.TakeWhile(n => Index > n.EndIndex).Select(n => n.Name).ToArray(); - return GetModules(names, includeMembers); - case NameExpression _: - case null: - return Analysis.ProjectState.GetModules().Select(ToCompletionItem); - default: - return Empty; - } - } + private IEnumerable GetModules() + => Analysis.ProjectState.GetModules().Select(ToCompletionItem); + + private IEnumerable GetModulesFromNode(DottedName name, bool includeMembers = false) => GetModules(GetNamesFromDottedName(name), includeMembers); + + private string[] GetNamesFromDottedName(DottedName name) => name.Names.TakeWhile(n => Index > n.EndIndex).Select(n => n.Name).ToArray(); private void SetApplicableSpanToLastToken(Node containingNode) { if (containingNode != null && Index >= containingNode.EndIndex) { @@ -297,7 +290,7 @@ private bool TryGetCompletionsInImport(ImportStatement import, out IEnumerable import.KeywordEndIndex) { - result = GetModulesFromNode(null); + result = GetModules(); return true; } @@ -306,18 +299,16 @@ private bool TryGetCompletionsInImport(ImportStatement import, out IEnumerable= name.StartIndex) { if (Index > name.EndIndex && name.EndIndex > name.StartIndex) { SetApplicableSpanToLastToken(import); result = Once(AsKeywordCompletion); - return true; - } - - if (Index >= name.StartIndex) { - Node = name.Names.MaybeEnumerate().LastOrDefault(n => n.StartIndex <= Index && Index <= n.EndIndex); + } else { + Node = name.Names.LastOrDefault(n => n.StartIndex <= Index && Index <= n.EndIndex); result = GetModulesFromNode(name); - return true; } + + return true; } } @@ -354,13 +345,13 @@ private bool TryGetCompletionsInFromImport(FromImportStatement fromImport, out I } if (fromImport.ImportIndex > fromImport.StartIndex) { - if (Index > fromImport.ImportIndex + 6) { - if (fromImport.Root == null) { - return true; + if (Index > fromImport.ImportIndex + 6 && Analysis.Scope.AnalysisValue is ModuleInfo moduleInfo) { + var root = fromImport.Root; + var rootName = root.MakeString(); + if (moduleInfo.TryGetModuleReference(rootName, out var moduleReference)) { + var moduleMembers = PythonAnalyzer.GetModuleMembers(Analysis.InterpreterContext, GetNamesFromDottedName(root), true, moduleReference.Module); + result = Once(StarCompletion).Concat(moduleMembers.Select(ToCompletionItem)); } - - var mods = GetModulesFromNode(fromImport.Root, true).ToArray(); - result = mods.Any() && fromImport.Names.Count <= 1 ? Once(StarCompletion).Concat(mods) : mods; return true; } @@ -374,31 +365,29 @@ private bool TryGetCompletionsInFromImport(FromImportStatement fromImport, out I } } - if (fromImport.Root != null) { - if (Index > fromImport.Root.EndIndex && fromImport.Root.EndIndex > fromImport.Root.StartIndex) { - if (Index > fromImport.EndIndex) { - // Only end up here for "from ... imp", and "imp" is not counted - // as part of our span - var token = Tokens.LastOrDefault(); - if (token.Key.End >= Index) { - ApplicableSpan = GetTokenSpan(token.Key); - } + if (Index > fromImport.Root.EndIndex && fromImport.Root.EndIndex > fromImport.Root.StartIndex) { + if (Index > fromImport.EndIndex) { + // Only end up here for "from ... imp", and "imp" is not counted + // as part of our span + var token = Tokens.LastOrDefault(); + if (token.Key.End >= Index) { + ApplicableSpan = GetTokenSpan(token.Key); } - - result = Once(ImportKeywordCompletion); - return true; } - if (Index >= fromImport.Root.StartIndex) { - Node = fromImport.Root.Names.MaybeEnumerate() - .LastOrDefault(n => n.StartIndex <= Index && Index <= n.EndIndex); - result = GetModulesFromNode(fromImport.Root); - return true; - } + result = Once(ImportKeywordCompletion); + return true; + } + + if (Index >= fromImport.Root.StartIndex) { + Node = fromImport.Root.Names.MaybeEnumerate() + .LastOrDefault(n => n.StartIndex <= Index && Index <= n.EndIndex); + result = GetModulesFromNode(fromImport.Root); + return true; } if (Index > fromImport.KeywordEndIndex) { - result = GetModulesFromNode(null); + result = GetModules(); return true; } diff --git a/src/LanguageServer/Impl/Services/ServiceManager.cs b/src/LanguageServer/Impl/Services/ServiceManager.cs index 1dd18dd9d..3a83f23d4 100644 --- a/src/LanguageServer/Impl/Services/ServiceManager.cs +++ b/src/LanguageServer/Impl/Services/ServiceManager.cs @@ -42,7 +42,7 @@ public virtual IServiceManager AddService(object service, Type type = null) { _disposeToken.ThrowIfDisposed(); type = type ?? service.GetType(); - Check.ArgumentNull(nameof(service), service); + Check.ArgumentNotNull(nameof(service), service); Check.InvalidOperation(() => _s.GetOrAdd(type, service) == service, Invariant($"Another instance of service of type {type} already added")); return this;