From 9ce186a0bb558e28c53895779ab619457c1ecb07 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Mon, 10 Dec 2018 12:19:41 -0800 Subject: [PATCH 01/11] Fix #470 --- src/Analysis/Engine/Impl/PythonAnalyzer.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Analysis/Engine/Impl/PythonAnalyzer.cs b/src/Analysis/Engine/Impl/PythonAnalyzer.cs index bc3f28077..c8b90d753 100644 --- a/src/Analysis/Engine/Impl/PythonAnalyzer.cs +++ b/src/Analysis/Engine/Impl/PythonAnalyzer.cs @@ -596,10 +596,11 @@ internal AnalysisValue GetCached(object key, Func maker) { internal BuiltinInstanceInfo GetInstance(IPythonType type) => GetBuiltinType(type).Instance; - internal BuiltinClassInfo GetBuiltinType(IPythonType type) => - (BuiltinClassInfo)GetCached(type, - () => MakeBuiltinType(type) - ) ?? ClassInfos[BuiltinTypeId.Object]; + internal BuiltinClassInfo GetBuiltinType(IPythonType type) + // Cached value may or may not be a class info. Previous calls to GetAnalysisValueFromObjects + // may have cached a different object for the type. For example, IPythonFunction would cache + // BuiltinFunctionInfo and not BuiltinClassInfo. Therefore, don't use direct cast. + => GetCached(type, () => MakeBuiltinType(type)) as BuiltinClassInfo ?? MakeBuiltinType(type); private BuiltinClassInfo MakeBuiltinType(IPythonType type) { switch (type.TypeId) { From a7a49ab145bef425f769c8785b3707efab6b8350 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 6 Feb 2019 15:55:14 -0800 Subject: [PATCH 02/11] Output syntax errors --- .../Impl/Extensions/DiagnosticsServiceExtensions.cs | 7 +++++++ src/Analysis/Ast/Impl/Modules/PythonModule.cs | 11 ++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs b/src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs index f0d0411c7..30873e907 100644 --- a/src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs +++ b/src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs @@ -14,6 +14,7 @@ // permissions and limitations under the License. using System; +using System.Collections.Generic; using Microsoft.Python.Analysis.Diagnostics; using Microsoft.Python.Core.Text; using Microsoft.Python.Parsing; @@ -22,5 +23,11 @@ namespace Microsoft.Python.Analysis { public static class DiagnosticsServiceExtensions { public static void Add(this IDiagnosticsService ds, Uri documentUri, string message, SourceSpan span, string errorCode, Severity severity) => ds.Add(documentUri, new DiagnosticsEntry(message, span, errorCode, severity)); + + public static void Add(this IDiagnosticsService ds, Uri documentUri, IEnumerable entries) { + foreach (var e in entries) { + ds.Add(documentUri, e); + } + } } } diff --git a/src/Analysis/Ast/Impl/Modules/PythonModule.cs b/src/Analysis/Ast/Impl/Modules/PythonModule.cs index 05192c8c5..7705d8e2c 100644 --- a/src/Analysis/Ast/Impl/Modules/PythonModule.cs +++ b/src/Analysis/Ast/Impl/Modules/PythonModule.cs @@ -56,6 +56,7 @@ protected enum State { private readonly DocumentBuffer _buffer = new DocumentBuffer(); private readonly CancellationTokenSource _allProcessingCts = new CancellationTokenSource(); private IReadOnlyList _parseErrors = Array.Empty(); + private readonly IDiagnosticsService _diagnosticsService; private string _documentation; // Must be null initially. private TaskCompletionSource _analysisTcs; @@ -72,14 +73,15 @@ protected enum State { protected State ContentState { get; set; } = State.None; protected PythonModule(string name, ModuleType moduleType, IServiceContainer services) { - Check.ArgumentNotNull(nameof(name), name); - Name = name; - Services = services; + Name = name ?? throw new ArgumentNullException(nameof(name)); + Services = services ?? throw new ArgumentNullException(nameof(services)); ModuleType = moduleType; Log = services?.GetService(); Interpreter = services?.GetService(); Analysis = new EmptyAnalysis(services, this); + + _diagnosticsService = services.GetService(); } protected PythonModule(string moduleName, string filePath, ModuleType moduleType, IPythonModule stub, IServiceContainer services) : @@ -376,7 +378,10 @@ private void Parse(CancellationToken cancellationToken) { throw new OperationCanceledException(); } _ast = ast; + _parseErrors = sink.Diagnostics; + _diagnosticsService.Add(Uri, _parseErrors); + _parsingTask = null; ContentState = State.Parsed; } From 094d2a05580d0e351aca9b7c2339ca35e3ce87a0 Mon Sep 17 00:00:00 2001 From: MikhailArkhipov Date: Wed, 6 Feb 2019 16:31:21 -0800 Subject: [PATCH 03/11] Properly clear --- .../Impl/Diagnostics/IDiagnosticsService.cs | 1 + src/Analysis/Ast/Impl/Modules/PythonModule.cs | 13 ++++- src/Analysis/Ast/Test/AnalysisTestBase.cs | 2 + .../Impl/Diagnostics/DiagnosticsService.cs | 55 ++++++++++++++----- 4 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs b/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs index 133e2fc55..bf172106f 100644 --- a/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs +++ b/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs @@ -20,6 +20,7 @@ namespace Microsoft.Python.Analysis.Diagnostics { public interface IDiagnosticsService { IReadOnlyList Diagnostics { get; } void Add(Uri documentUri, DiagnosticsEntry entry); + void Clear(Uri documentUri); int PublishingDelay { get; set; } } } diff --git a/src/Analysis/Ast/Impl/Modules/PythonModule.cs b/src/Analysis/Ast/Impl/Modules/PythonModule.cs index 7705d8e2c..bc73ebfa1 100644 --- a/src/Analysis/Ast/Impl/Modules/PythonModule.cs +++ b/src/Analysis/Ast/Impl/Modules/PythonModule.cs @@ -252,6 +252,7 @@ private void LoadContent(string content) { public void Dispose() => Dispose(true); protected virtual void Dispose(bool disposing) { + _diagnosticsService.Clear(Uri); _allProcessingCts.Cancel(); _allProcessingCts.Dispose(); } @@ -290,7 +291,7 @@ public async Task GetAstAsync(CancellationToken cancellationToken = d Task t = null; while (true) { lock (AnalysisLock) { - if(t == _parsingTask) { + if (t == _parsingTask) { break; } cancellationToken.ThrowIfCancellationRequested(); @@ -378,9 +379,15 @@ private void Parse(CancellationToken cancellationToken) { throw new OperationCanceledException(); } _ast = ast; - _parseErrors = sink.Diagnostics; - _diagnosticsService.Add(Uri, _parseErrors); + + if (ModuleType == ModuleType.User) { + if (_parseErrors.Count > 0) { + _diagnosticsService.Add(Uri, _parseErrors); + } else { + _diagnosticsService.Clear(Uri); + } + } _parsingTask = null; ContentState = State.Parsed; diff --git a/src/Analysis/Ast/Test/AnalysisTestBase.cs b/src/Analysis/Ast/Test/AnalysisTestBase.cs index 51682bc1d..e83b570e0 100644 --- a/src/Analysis/Ast/Test/AnalysisTestBase.cs +++ b/src/Analysis/Ast/Test/AnalysisTestBase.cs @@ -158,6 +158,8 @@ public IReadOnlyList Diagnostics { } } + public void Clear(Uri uri) { } + public void Add(Uri documentUri, DiagnosticsEntry entry) { lock (_lock) { if (!_diagnostics.TryGetValue(documentUri, out var list)) { diff --git a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs index 77fadecaf..e3df361c9 100644 --- a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs +++ b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs @@ -25,13 +25,13 @@ using StreamJsonRpc; namespace Microsoft.Python.LanguageServer.Diagnostics { - internal sealed class DiagnosticsService : IDiagnosticsService, IDisposable { private readonly Dictionary> _pendingDiagnostics = new Dictionary>(); private readonly DisposableBag _disposables = DisposableBag.Create(); private readonly JsonRpc _rpc; private readonly object _lock = new object(); private DateTime _lastChangeTime; + private bool _changed; public DiagnosticsService(IServiceContainer services) { var idleTimeService = services.GetService(); @@ -48,52 +48,77 @@ public DiagnosticsService(IServiceContainer services) { #region IDiagnosticsService public IReadOnlyList Diagnostics { get { - lock(_lock) { + lock (_lock) { return _pendingDiagnostics.Values.SelectMany().ToArray(); } } } public void Add(Uri documentUri, DiagnosticsEntry entry) { - lock(_lock) { - if(!_pendingDiagnostics.TryGetValue(documentUri, out var list)) { + lock (_lock) { + if (!_pendingDiagnostics.TryGetValue(documentUri, out var list)) { _pendingDiagnostics[documentUri] = list = new List(); } list.Add(entry); _lastChangeTime = DateTime.Now; + _changed = true; + } + } + + public void Clear(Uri documentUri) { + lock (_lock) { + ClearPendingDiagnostics(documentUri); + _pendingDiagnostics.Remove(documentUri); } } public int PublishingDelay { get; set; } #endregion - public void Dispose() => _disposables.TryDispose(); + public void Dispose() { + _disposables.TryDispose(); + ClearAllPendingDiagnostics(); + } private void OnClosing(object sender, EventArgs e) => Dispose(); private void OnIdle(object sender, EventArgs e) { - if (_pendingDiagnostics.Count > 0 && (DateTime.Now - _lastChangeTime).TotalMilliseconds > PublishingDelay) { + if (_changed && (DateTime.Now - _lastChangeTime).TotalMilliseconds > PublishingDelay) { PublishPendingDiagnostics(); } } private void PublishPendingDiagnostics() { - List>> list; - lock (_lock) { - list = _pendingDiagnostics.ToList(); + foreach (var kvp in _pendingDiagnostics) { + var parameters = new PublishDiagnosticsParams { + uri = kvp.Key, + diagnostics = kvp.Value.Select(x => ToDiagnostic(kvp.Key, x)).ToArray() + }; + _rpc.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", parameters).DoNotWait(); + } _pendingDiagnostics.Clear(); + _changed = false; } + } - foreach (var kvp in list) { - var parameters = new PublishDiagnosticsParams { - uri = kvp.Key, - diagnostics = kvp.Value.Select(x => ToDiagnostic(kvp.Key, x)).ToArray() - }; - _rpc.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", parameters).DoNotWait(); + private void ClearAllPendingDiagnostics() { + lock (_lock) { + foreach (var uri in _pendingDiagnostics.Keys) { + ClearPendingDiagnostics(uri); + } + _changed = false; } } + private void ClearPendingDiagnostics(Uri uri) { + var parameters = new PublishDiagnosticsParams { + uri = uri, + diagnostics = Array.Empty() + }; + _rpc.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", parameters).DoNotWait(); + } + private static Diagnostic ToDiagnostic(Uri uri, DiagnosticsEntry e) { DiagnosticSeverity s; switch (e.Severity) { From c8ef668364a27ae64f0d6e2ffe5276c8f1d347bb Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Wed, 6 Feb 2019 22:45:29 -0800 Subject: [PATCH 04/11] - Fix async issue with analysis completion - Clean up diagnostics service interface - Use real DS in tests --- .../Definitions/IExpressionEvaluator.cs | 4 +- .../Analyzer/Evaluation/ExpressionEval.cs | 13 ++-- .../Ast/Impl/Analyzer/PythonAnalyzer.cs | 13 ++-- .../Impl/Diagnostics/IDiagnosticsPublisher.cs | 23 ------ .../Impl/Diagnostics/IDiagnosticsService.cs | 22 +++++- .../DiagnosticsServiceExtensions.cs | 33 -------- src/Analysis/Ast/Impl/Modules/PythonModule.cs | 54 +++++++------ src/Analysis/Ast/Test/AnalysisTestBase.cs | 40 +++------- .../Impl/Diagnostics/DiagnosticsService.cs | 60 ++++++-------- src/LanguageServer/Test/DiagnosticsTests.cs | 78 +++++++++++++++++++ .../Test/LanguageServerTestBase.cs | 4 + 11 files changed, 182 insertions(+), 162 deletions(-) delete mode 100644 src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsPublisher.cs delete mode 100644 src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs create mode 100644 src/LanguageServer/Test/DiagnosticsTests.cs diff --git a/src/Analysis/Ast/Impl/Analyzer/Definitions/IExpressionEvaluator.cs b/src/Analysis/Ast/Impl/Analyzer/Definitions/IExpressionEvaluator.cs index 3c44ba8dc..0f89b7572 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Definitions/IExpressionEvaluator.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Definitions/IExpressionEvaluator.cs @@ -14,8 +14,10 @@ // permissions and limitations under the License. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Python.Analysis.Diagnostics; using Microsoft.Python.Analysis.Types; using Microsoft.Python.Analysis.Values; using Microsoft.Python.Core; @@ -59,12 +61,12 @@ public interface IExpressionEvaluator { IMember LookupNameInScopes(string name, out IScope scope); - IPythonType GetTypeFromPepHint(Node node); IPythonType GetTypeFromString(string typeString); PythonAst Ast { get; } IPythonModule Module { get; } IPythonInterpreter Interpreter { get; } IServiceContainer Services { get; } + IEnumerable Diagnostics { get; } } } diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs index 9e4ba96c1..a78c75dbd 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs @@ -33,9 +33,10 @@ namespace Microsoft.Python.Analysis.Analyzer.Evaluation { /// Helper class that provides methods for looking up variables /// and types in a chain of scopes during analysis. /// - internal sealed partial class ExpressionEval : IExpressionEvaluator { + internal sealed partial class ExpressionEval { private readonly Stack _openScopes = new Stack(); - private readonly IDiagnosticsService _diagnostics; + private readonly List _diagnostics = new List(); + private readonly object _lock = new object(); public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAst ast) { Services = services ?? throw new ArgumentNullException(nameof(services)); @@ -47,7 +48,6 @@ public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAs DefaultLookupOptions = LookupOptions.Normal; //Log = services.GetService(); - _diagnostics = services.GetService(); } public LookupOptions DefaultLookupOptions { get; set; } @@ -69,6 +69,7 @@ public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAs IScope IExpressionEvaluator.CurrentScope => CurrentScope; IGlobalScope IExpressionEvaluator.GlobalScope => GlobalScope; public LocationInfo GetLocation(Node node) => node?.GetLocation(Module, Ast) ?? LocationInfo.Empty; + public IEnumerable Diagnostics => _diagnostics; public Task GetValueFromExpressionAsync(Expression expr, CancellationToken cancellationToken = default) => GetValueFromExpressionAsync(expr, DefaultLookupOptions, cancellationToken); @@ -228,11 +229,11 @@ private async Task GetValueFromConditionalAsync(ConditionalExpression e return trueValue ?? falseValue; } - private void AddDiagnostics(Uri documentUri, IEnumerable entries) { + private void ReportDiagnostics(Uri documentUri, IEnumerable entries) { // Do not add if module is library, etc. Only handle user code. if (Module.ModuleType == ModuleType.User) { - foreach (var e in entries) { - _diagnostics?.Add(documentUri, e); + lock (_lock) { + _diagnostics.AddRange(entries); } } } diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs index 1b8960ff8..c83b7d60b 100644 --- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs +++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs @@ -55,7 +55,6 @@ public PythonAnalyzer(IServiceManager services, string root) { public async Task AnalyzeDocumentAsync(IDocument document, CancellationToken cancellationToken) { var node = new DependencyChainNode(document); using (var cts = CancellationTokenSource.CreateLinkedTokenSource(_globalCts.Token, cancellationToken)) { - node.Analyzable.NotifyAnalysisPending(); try { var analysis = await AnalyzeAsync(node, cts.Token); node.Analyzable.NotifyAnalysisComplete(analysis); @@ -78,17 +77,21 @@ public async Task AnalyzeDocumentDependencyChainAsync(IDocument document, Cancel using (var cts = CancellationTokenSource.CreateLinkedTokenSource(_globalCts.Token, cancellationToken)) { var dependencyRoot = await _dependencyResolver.GetDependencyChainAsync(document, cts.Token); // Notify each dependency that the analysis is now pending - NotifyAnalysisPending(dependencyRoot); + NotifyAnalysisPending(document, dependencyRoot); cts.Token.ThrowIfCancellationRequested(); await AnalyzeChainAsync(dependencyRoot, cts.Token); } } - private void NotifyAnalysisPending(IDependencyChainNode node) { - node.Analyzable.NotifyAnalysisPending(); + private void NotifyAnalysisPending(IDocument document, IDependencyChainNode node) { + // Notify each dependency that the analysis is now pending except the source + // since if document has changed, it already incremented its expected analysis. + if (node.Analyzable != document) { + node.Analyzable.NotifyAnalysisPending(); + } foreach (var c in node.Children) { - NotifyAnalysisPending(c); + NotifyAnalysisPending(document, c); } } diff --git a/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsPublisher.cs b/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsPublisher.cs deleted file mode 100644 index 7f428e975..000000000 --- a/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsPublisher.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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 Microsoft.Python.Core.Text; -using Microsoft.Python.Parsing; - -namespace Microsoft.Python.Analysis.Diagnostics { - public interface IDiagnosticsPublisher { - void AddDiagnostics(string message, SourceSpan span, int errorCode, Severity severity); - } -} diff --git a/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs b/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs index bf172106f..7de168b73 100644 --- a/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs +++ b/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs @@ -18,9 +18,25 @@ namespace Microsoft.Python.Analysis.Diagnostics { public interface IDiagnosticsService { - IReadOnlyList Diagnostics { get; } - void Add(Uri documentUri, DiagnosticsEntry entry); - void Clear(Uri documentUri); + /// + /// Current complete diagnostics. + /// + IReadOnlyDictionary> Diagnostics { get; } + + /// + /// Replaces diagnostics for the document by the new set. + /// + void Replace(Uri documentUri, IEnumerable entries); + + /// + /// Removes document from the diagnostics report. Typically when document closes. + /// + void Remove(Uri documentUri); + + /// + /// Defines delay in milliseconds from the idle time start and + /// the diagnostic publishing to the client. + /// int PublishingDelay { get; set; } } } diff --git a/src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs b/src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs deleted file mode 100644 index 30873e907..000000000 --- a/src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// 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.Collections.Generic; -using Microsoft.Python.Analysis.Diagnostics; -using Microsoft.Python.Core.Text; -using Microsoft.Python.Parsing; - -namespace Microsoft.Python.Analysis { - public static class DiagnosticsServiceExtensions { - public static void Add(this IDiagnosticsService ds, Uri documentUri, string message, SourceSpan span, string errorCode, Severity severity) - => ds.Add(documentUri, new DiagnosticsEntry(message, span, errorCode, severity)); - - public static void Add(this IDiagnosticsService ds, Uri documentUri, IEnumerable entries) { - foreach (var e in entries) { - ds.Add(documentUri, e); - } - } - } -} diff --git a/src/Analysis/Ast/Impl/Modules/PythonModule.cs b/src/Analysis/Ast/Impl/Modules/PythonModule.cs index bc73ebfa1..c0f391e9e 100644 --- a/src/Analysis/Ast/Impl/Modules/PythonModule.cs +++ b/src/Analysis/Ast/Impl/Modules/PythonModule.cs @@ -220,13 +220,13 @@ protected virtual string LoadContent() { private void InitializeContent(string content) { lock (AnalysisLock) { LoadContent(content); + var startParse = ContentState < State.Parsing && _parsingTask == null; var startAnalysis = startParse | (ContentState < State.Analyzing && _analysisTcs?.Task == null); if (startAnalysis) { - _analysisTcs = new TaskCompletionSource(); + ExpectNewAnalysis(); } - if (startParse) { Parse(); } @@ -252,7 +252,7 @@ private void LoadContent(string content) { public void Dispose() => Dispose(true); protected virtual void Dispose(bool disposing) { - _diagnosticsService.Clear(Uri); + _diagnosticsService?.Remove(Uri); _allProcessingCts.Cancel(); _allProcessingCts.Dispose(); } @@ -317,10 +317,8 @@ public async Task GetAstAsync(CancellationToken cancellationToken = d public void Update(IEnumerable changes) { lock (AnalysisLock) { - ExpectedAnalysisVersion++; - + ExpectNewAnalysis(); _linkedAnalysisCts?.Cancel(); - _analysisTcs = new TaskCompletionSource(); _parseCts?.Cancel(); _parseCts = new CancellationTokenSource(); @@ -355,7 +353,7 @@ private void Parse() { } private void Parse(CancellationToken cancellationToken) { - var sink = new CollectingErrorSink(); + CollectingErrorSink sink = null; int version; Parser parser; @@ -363,10 +361,14 @@ private void Parse(CancellationToken cancellationToken) { lock (AnalysisLock) { version = _buffer.Version; - parser = Parser.CreateParser(new StringReader(_buffer.Text), Interpreter.LanguageVersion, new ParserOptions { - StubFile = FilePath != null && Path.GetExtension(FilePath).Equals(".pyi", FileSystem.StringComparison), - ErrorSink = sink - }); + var options = new ParserOptions { + StubFile = FilePath != null && Path.GetExtension(FilePath).Equals(".pyi", FileSystem.StringComparison) + }; + if (ModuleType == ModuleType.User) { + sink = new CollectingErrorSink(); + options.ErrorSink = sink; + } + parser = Parser.CreateParser(new StringReader(_buffer.Text), Interpreter.LanguageVersion, options); } var ast = parser.ParseFile(); @@ -379,14 +381,11 @@ private void Parse(CancellationToken cancellationToken) { throw new OperationCanceledException(); } _ast = ast; - _parseErrors = sink.Diagnostics; + _parseErrors = sink?.Diagnostics ?? Array.Empty(); - if (ModuleType == ModuleType.User) { - if (_parseErrors.Count > 0) { - _diagnosticsService.Add(Uri, _parseErrors); - } else { - _diagnosticsService.Clear(Uri); - } + // Do not report issues with libraries or stubs + if (sink != null) { + _diagnosticsService?.Replace(Uri, _parseErrors); } _parsingTask = null; @@ -440,11 +439,10 @@ public override void Add(string message, SourceSpan span, int errorCode, Severit public void NotifyAnalysisPending() { lock (AnalysisLock) { // The notification comes from the analyzer when it needs to invalidate - // current analysis since one of the dependencies changed. Upon text - // buffer change the version may be incremented twice - once in Update() - // and then here. This is normal. - ExpectedAnalysisVersion++; - _analysisTcs = _analysisTcs ?? new TaskCompletionSource(); + // current analysis since one of the dependencies changed. If text + // buffer changed then the notification won't come since the analyzer + // filters out original initiator of the analysis. + ExpectNewAnalysis(); //Log?.Log(TraceEventType.Verbose, $"Analysis pending: {Name}"); } } @@ -460,10 +458,11 @@ public virtual bool NotifyAnalysisComplete(IDocumentAnalysis analysis) { // to perform additional actions on the completed analysis such // as declare additional variables, etc. OnAnalysisComplete(); + ContentState = State.Analyzed; - _analysisTcs.TrySetResult(analysis); + var tcs = _analysisTcs; _analysisTcs = null; - ContentState = State.Analyzed; + tcs.TrySetResult(analysis); NewAnalysis?.Invoke(this, EventArgs.Empty); return true; @@ -489,6 +488,11 @@ public Task GetAnalysisAsync(CancellationToken cancellationTo } #endregion + private void ExpectNewAnalysis() { + ExpectedAnalysisVersion++; + _analysisTcs = _analysisTcs ?? new TaskCompletionSource(); + } + private string TryGetDocFromModuleInitFile() { if (string.IsNullOrEmpty(FilePath) || !FileSystem.FileExists(FilePath)) { return string.Empty; diff --git a/src/Analysis/Ast/Test/AnalysisTestBase.cs b/src/Analysis/Ast/Test/AnalysisTestBase.cs index e83b570e0..89422ea18 100644 --- a/src/Analysis/Ast/Test/AnalysisTestBase.cs +++ b/src/Analysis/Ast/Test/AnalysisTestBase.cs @@ -40,17 +40,20 @@ namespace Microsoft.Python.Analysis.Tests { public abstract class AnalysisTestBase { protected TestLogger TestLogger { get; } = new TestLogger(); + protected ServiceManager _sm; + + protected virtual IDiagnosticsService GetDiagnosticsService(IServiceContainer s) => null; protected virtual ServiceManager CreateServiceManager() { - var sm = new ServiceManager(); + _sm = new ServiceManager(); var platform = new OSPlatform(); - sm + _sm .AddService(TestLogger) .AddService(platform) .AddService(new FileSystem(platform)); - return sm; + return _sm; } protected string GetAnalysisTestDataFilesPath() => TestData.GetPath(Path.Combine("TestData", "AstAnalysis")); @@ -65,7 +68,10 @@ protected async Task CreateServicesAsync(string root, Interpret var sm = CreateServiceManager(); - sm.AddService(new DiagnosticsService()); + var ds = GetDiagnosticsService(_sm); + if (ds != null) { + sm.AddService(ds); + } TestLogger.Log(TraceEventType.Information, "Create TestDependencyResolver"); var dependencyResolver = new TestDependencyResolver(); @@ -145,31 +151,5 @@ private sealed class TestDependencyResolver : IDependencyResolver { public Task GetDependencyChainAsync(IDocument document, CancellationToken cancellationToken) => Task.FromResult(new DependencyChainNode(document)); } - - protected sealed class DiagnosticsService : IDiagnosticsService { - private readonly Dictionary> _diagnostics = new Dictionary>(); - private readonly object _lock = new object(); - - public IReadOnlyList Diagnostics { - get { - lock (_lock) { - return _diagnostics.Values.SelectMany().ToArray(); - } - } - } - - public void Clear(Uri uri) { } - - public void Add(Uri documentUri, DiagnosticsEntry entry) { - lock (_lock) { - if (!_diagnostics.TryGetValue(documentUri, out var list)) { - _diagnostics[documentUri] = list = new List(); - } - list.Add(entry); - } - } - - public int PublishingDelay { get; set; } - } } } diff --git a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs index e3df361c9..30002b1d9 100644 --- a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs +++ b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs @@ -26,7 +26,7 @@ namespace Microsoft.Python.LanguageServer.Diagnostics { internal sealed class DiagnosticsService : IDiagnosticsService, IDisposable { - private readonly Dictionary> _pendingDiagnostics = new Dictionary>(); + private readonly Dictionary> _diagnostics = new Dictionary>(); private readonly DisposableBag _disposables = DisposableBag.Create(); private readonly JsonRpc _rpc; private readonly object _lock = new object(); @@ -35,40 +35,39 @@ internal sealed class DiagnosticsService : IDiagnosticsService, IDisposable { public DiagnosticsService(IServiceContainer services) { var idleTimeService = services.GetService(); - idleTimeService.Idle += OnIdle; - idleTimeService.Closing += OnClosing; - _rpc = services.GetService(); + if (idleTimeService != null) { + idleTimeService.Idle += OnIdle; + idleTimeService.Closing += OnClosing; - _disposables - .Add(() => idleTimeService.Idle -= OnIdle) - .Add(() => idleTimeService.Idle -= OnClosing); + _disposables + .Add(() => idleTimeService.Idle -= OnIdle) + .Add(() => idleTimeService.Idle -= OnClosing); + } + _rpc = services.GetService(); } #region IDiagnosticsService - public IReadOnlyList Diagnostics { + public IReadOnlyDictionary> Diagnostics { get { lock (_lock) { - return _pendingDiagnostics.Values.SelectMany().ToArray(); + return _diagnostics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value as IReadOnlyList); } } } - public void Add(Uri documentUri, DiagnosticsEntry entry) { + public void Replace(Uri documentUri, IEnumerable entries) { lock (_lock) { - if (!_pendingDiagnostics.TryGetValue(documentUri, out var list)) { - _pendingDiagnostics[documentUri] = list = new List(); - } - list.Add(entry); + _diagnostics[documentUri] = entries.ToList(); _lastChangeTime = DateTime.Now; _changed = true; } } - public void Clear(Uri documentUri) { + public void Remove(Uri documentUri) { lock (_lock) { - ClearPendingDiagnostics(documentUri); - _pendingDiagnostics.Remove(documentUri); + _diagnostics.Remove(documentUri); + _changed = true; } } @@ -77,49 +76,38 @@ public void Clear(Uri documentUri) { public void Dispose() { _disposables.TryDispose(); - ClearAllPendingDiagnostics(); + ClearAllDiagnostics(); } private void OnClosing(object sender, EventArgs e) => Dispose(); private void OnIdle(object sender, EventArgs e) { if (_changed && (DateTime.Now - _lastChangeTime).TotalMilliseconds > PublishingDelay) { - PublishPendingDiagnostics(); + PublishDiagnostics(); } } - private void PublishPendingDiagnostics() { + private void PublishDiagnostics() { lock (_lock) { - foreach (var kvp in _pendingDiagnostics) { + foreach (var kvp in _diagnostics) { var parameters = new PublishDiagnosticsParams { uri = kvp.Key, - diagnostics = kvp.Value.Select(x => ToDiagnostic(kvp.Key, x)).ToArray() + diagnostics = kvp.Value.Select(ToDiagnostic).ToArray() }; _rpc.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", parameters).DoNotWait(); } - _pendingDiagnostics.Clear(); _changed = false; } } - private void ClearAllPendingDiagnostics() { + private void ClearAllDiagnostics() { lock (_lock) { - foreach (var uri in _pendingDiagnostics.Keys) { - ClearPendingDiagnostics(uri); - } + _diagnostics.Clear(); _changed = false; } } - private void ClearPendingDiagnostics(Uri uri) { - var parameters = new PublishDiagnosticsParams { - uri = uri, - diagnostics = Array.Empty() - }; - _rpc.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", parameters).DoNotWait(); - } - - private static Diagnostic ToDiagnostic(Uri uri, DiagnosticsEntry e) { + private static Diagnostic ToDiagnostic(DiagnosticsEntry e) { DiagnosticSeverity s; switch (e.Severity) { case Severity.Warning: diff --git a/src/LanguageServer/Test/DiagnosticsTests.cs b/src/LanguageServer/Test/DiagnosticsTests.cs new file mode 100644 index 000000000..3a5eb2d7d --- /dev/null +++ b/src/LanguageServer/Test/DiagnosticsTests.cs @@ -0,0 +1,78 @@ +// 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 FluentAssertions; +using Microsoft.Python.Analysis.Diagnostics; +using Microsoft.Python.Analysis.Documents; +using Microsoft.Python.Core.Text; +using Microsoft.Python.LanguageServer.Sources; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace Microsoft.Python.LanguageServer.Tests { + [TestClass] + public class DiagnosticsTests : LanguageServerTestBase { + public TestContext TestContext { get; set; } + + [TestInitialize] + public void TestInitialize() + => TestEnvironmentImpl.TestInitialize($"{TestContext.FullyQualifiedTestClassName}.{TestContext.TestName}"); + + [TestCleanup] + public void Cleanup() => TestEnvironmentImpl.TestCleanup(); + + [TestMethod, Priority(0)] + public async Task BasicChange() { + const string code = @"x = "; + + var analysis = await GetAnalysisAsync(code); + var ds = _sm.GetService(); + + var doc = analysis.Document; + ds.Diagnostics[doc.Uri].Count.Should().Be(1); + + doc.Update(new [] {new DocumentChange { + InsertedText = "1", + ReplacedSpan = new SourceSpan(1, 5, 1, 5) + } }); + + await doc.GetAnalysisAsync(); + ds.Diagnostics[doc.Uri].Count.Should().Be(0); + + doc.Update(new[] {new DocumentChange { + InsertedText = string.Empty, + ReplacedSpan = new SourceSpan(1, 5, 1, 6) + } }); + + await doc.GetAnalysisAsync(); + ds.Diagnostics[doc.Uri].Count.Should().Be(1); + } + + [TestMethod, Priority(0)] + public async Task CloseDocument() { + const string code = @"x = "; + + var analysis = await GetAnalysisAsync(code); + var ds = _sm.GetService(); + + var doc = analysis.Document; + ds.Diagnostics[doc.Uri].Count.Should().Be(1); + doc.Dispose(); + + ds.Diagnostics.TryGetValue(doc.Uri, out _).Should().BeFalse(); + } + } +} diff --git a/src/LanguageServer/Test/LanguageServerTestBase.cs b/src/LanguageServer/Test/LanguageServerTestBase.cs index 8f32a3a54..88c882da8 100644 --- a/src/LanguageServer/Test/LanguageServerTestBase.cs +++ b/src/LanguageServer/Test/LanguageServerTestBase.cs @@ -13,10 +13,14 @@ // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. +using Microsoft.Python.Analysis.Diagnostics; using Microsoft.Python.Analysis.Tests; +using Microsoft.Python.Core; +using Microsoft.Python.LanguageServer.Diagnostics; namespace Microsoft.Python.LanguageServer.Tests { public abstract class LanguageServerTestBase : AnalysisTestBase { protected static readonly ServerSettings ServerSettings = new ServerSettings(); + protected override IDiagnosticsService GetDiagnosticsService(IServiceContainer s) => new DiagnosticsService(s); } } From d7a8b42dd957f357bf8aad2d73757b04dccbed0e Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 7 Feb 2019 12:46:13 -0800 Subject: [PATCH 05/11] Add publishing test Add NSubstitute Move all services to the same namespace. --- .../Ast/Impl/Analyzer/PythonAnalyzer.cs | 2 +- .../Ast/Impl/Analyzer/PythonInterpreter.cs | 2 +- src/Analysis/Ast/Test/AnalysisTestBase.cs | 29 ++++-- .../Microsoft.Python.Analysis.Tests.csproj | 5 + src/Core/Impl/Services/IClientApplication.cs | 28 ++++++ .../{Shell => Services}/IProgressService.cs | 2 +- .../{Shell => Services}/IServiceContainer.cs | 0 .../{Shell => Services}/IServiceManager.cs | 2 +- .../{Shell => Services}/ITelemetryService.cs | 2 +- .../Impl/{Shell => Services}/IUIService.cs | 2 +- src/Core/Impl/Services/ServiceManager.cs | 1 - .../Impl/Diagnostics/DiagnosticsService.cs | 14 +-- .../Impl/Implementation/Server.cs | 2 +- src/LanguageServer/Impl/LanguageServer.cs | 2 +- src/LanguageServer/Impl/Program.cs | 11 ++- .../Impl/Services/ClientApplication.cs | 38 ++++++++ src/LanguageServer/Impl/Services/Logger.cs | 10 +- .../Impl/Services/ProgressService.cs | 23 +++-- .../Impl/Services/TelemetryService.cs | 11 +-- src/LanguageServer/Impl/Services/UIService.cs | 15 ++- src/LanguageServer/Impl/Telemetry.cs | 4 +- src/LanguageServer/Test/DiagnosticsTests.cs | 95 ++++++++++++++++++- ...crosoft.Python.LanguageServer.Tests.csproj | 1 + 23 files changed, 235 insertions(+), 66 deletions(-) create mode 100644 src/Core/Impl/Services/IClientApplication.cs rename src/Core/Impl/{Shell => Services}/IProgressService.cs (96%) rename src/Core/Impl/{Shell => Services}/IServiceContainer.cs (100%) rename src/Core/Impl/{Shell => Services}/IServiceManager.cs (97%) rename src/Core/Impl/{Shell => Services}/ITelemetryService.cs (96%) rename src/Core/Impl/{Shell => Services}/IUIService.cs (97%) create mode 100644 src/LanguageServer/Impl/Services/ClientApplication.cs diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs index c83b7d60b..5c90c2038 100644 --- a/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs +++ b/src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs @@ -22,7 +22,7 @@ using Microsoft.Python.Core; using Microsoft.Python.Core.Diagnostics; using Microsoft.Python.Core.Logging; -using Microsoft.Python.Core.Shell; +using Microsoft.Python.Core.Services; namespace Microsoft.Python.Analysis.Analyzer { public sealed class PythonAnalyzer : IPythonAnalyzer, IDisposable { diff --git a/src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs b/src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs index 3a758ff63..5b1ff15e8 100644 --- a/src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs +++ b/src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs @@ -23,7 +23,7 @@ using Microsoft.Python.Analysis.Specializations.Typing; using Microsoft.Python.Analysis.Types; using Microsoft.Python.Core; -using Microsoft.Python.Core.Shell; +using Microsoft.Python.Core.Services; using Microsoft.Python.Parsing; namespace Microsoft.Python.Analysis.Analyzer { diff --git a/src/Analysis/Ast/Test/AnalysisTestBase.cs b/src/Analysis/Ast/Test/AnalysisTestBase.cs index 89422ea18..3d69856dc 100644 --- a/src/Analysis/Ast/Test/AnalysisTestBase.cs +++ b/src/Analysis/Ast/Test/AnalysisTestBase.cs @@ -28,6 +28,7 @@ using Microsoft.Python.Analysis.Documents; using Microsoft.Python.Analysis.Modules; using Microsoft.Python.Core; +using Microsoft.Python.Core.Idle; using Microsoft.Python.Core.IO; using Microsoft.Python.Core.OS; using Microsoft.Python.Core.Services; @@ -35,25 +36,26 @@ using Microsoft.Python.Core.Tests; using Microsoft.Python.Parsing; using Microsoft.Python.Parsing.Tests; +using NSubstitute; using TestUtilities; namespace Microsoft.Python.Analysis.Tests { public abstract class AnalysisTestBase { protected TestLogger TestLogger { get; } = new TestLogger(); - protected ServiceManager _sm; + protected ServiceManager Services { get; private set; } protected virtual IDiagnosticsService GetDiagnosticsService(IServiceContainer s) => null; protected virtual ServiceManager CreateServiceManager() { - _sm = new ServiceManager(); + Services = new ServiceManager(); var platform = new OSPlatform(); - _sm + Services .AddService(TestLogger) .AddService(platform) .AddService(new FileSystem(platform)); - return _sm; + return Services; } protected string GetAnalysisTestDataFilesPath() => TestData.GetPath(Path.Combine("TestData", "AstAnalysis")); @@ -68,7 +70,13 @@ protected async Task CreateServicesAsync(string root, Interpret var sm = CreateServiceManager(); - var ds = GetDiagnosticsService(_sm); + var clientApp = Substitute.For(); + sm.AddService(clientApp); + + var idle = Substitute.For(); + sm.AddService(idle); + + var ds = GetDiagnosticsService(Services); if (ds != null) { sm.AddService(ds); } @@ -100,7 +108,6 @@ protected async Task GetAnalysisAsync( InterpreterConfiguration configuration = null, string modulePath = null) { - var moduleUri = TestData.GetDefaultModuleUri(); modulePath = modulePath ?? TestData.GetDefaultModulePath(); var moduleName = Path.GetFileNameWithoutExtension(modulePath); var moduleDirectory = Path.GetDirectoryName(modulePath); @@ -109,14 +116,20 @@ protected async Task GetAnalysisAsync( return await GetAnalysisAsync(code, services, moduleName, modulePath); } + protected async Task GetNextAnalysisAsync(string code, string modulePath = null) { + modulePath = modulePath ?? TestData.GetNextModulePath(); + var moduleName = Path.GetFileNameWithoutExtension(modulePath); + return await GetAnalysisAsync(code, Services, moduleName, modulePath); + } + protected async Task GetAnalysisAsync( string code, IServiceContainer services, string moduleName = null, string modulePath = null) { - var moduleUri = TestData.GetDefaultModuleUri(); - modulePath = modulePath ?? TestData.GetDefaultModulePath(); + var moduleUri = modulePath != null ? new Uri(modulePath) : TestData.GetDefaultModuleUri(); + modulePath = modulePath ?? TestData .GetDefaultModulePath(); moduleName = moduleName ?? Path.GetFileNameWithoutExtension(modulePath); IDocument doc; diff --git a/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj b/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj index 345a3ce1d..d9a827933 100644 --- a/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj +++ b/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj @@ -41,6 +41,11 @@ + + + ..\..\..\..\..\Users\mikhaila\.nuget\packages\nsubstitute\4.0.0\lib\netstandard2.0\NSubstitute.dll + + diff --git a/src/Core/Impl/Services/IClientApplication.cs b/src/Core/Impl/Services/IClientApplication.cs new file mode 100644 index 000000000..00a52a713 --- /dev/null +++ b/src/Core/Impl/Services/IClientApplication.cs @@ -0,0 +1,28 @@ +// 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; +using System.Threading.Tasks; + +namespace Microsoft.Python.Core.Services { + /// + /// Represents client application + /// + public interface IClientApplication { + Task NotifyAsync(string targetName, params object[] arguments); + Task NotifyWithParameterObjectAsync(string targetName, object argument = null); + Task InvokeWithParameterObjectAsync(string targetName, object argument = null, CancellationToken cancellationToken = default); + } +} diff --git a/src/Core/Impl/Shell/IProgressService.cs b/src/Core/Impl/Services/IProgressService.cs similarity index 96% rename from src/Core/Impl/Shell/IProgressService.cs rename to src/Core/Impl/Services/IProgressService.cs index 0932519ca..352690c09 100644 --- a/src/Core/Impl/Shell/IProgressService.cs +++ b/src/Core/Impl/Services/IProgressService.cs @@ -16,7 +16,7 @@ using System; using System.Threading.Tasks; -namespace Microsoft.Python.Core.Shell { +namespace Microsoft.Python.Core.Services { /// /// Progress reporting service /// diff --git a/src/Core/Impl/Shell/IServiceContainer.cs b/src/Core/Impl/Services/IServiceContainer.cs similarity index 100% rename from src/Core/Impl/Shell/IServiceContainer.cs rename to src/Core/Impl/Services/IServiceContainer.cs diff --git a/src/Core/Impl/Shell/IServiceManager.cs b/src/Core/Impl/Services/IServiceManager.cs similarity index 97% rename from src/Core/Impl/Shell/IServiceManager.cs rename to src/Core/Impl/Services/IServiceManager.cs index 0ab68054e..a5ea038b6 100644 --- a/src/Core/Impl/Shell/IServiceManager.cs +++ b/src/Core/Impl/Services/IServiceManager.cs @@ -16,7 +16,7 @@ using System; -namespace Microsoft.Python.Core.Shell { +namespace Microsoft.Python.Core.Services { public interface IServiceManager : IServiceContainer, IDisposable { /// /// Adds service instance diff --git a/src/Core/Impl/Shell/ITelemetryService.cs b/src/Core/Impl/Services/ITelemetryService.cs similarity index 96% rename from src/Core/Impl/Shell/ITelemetryService.cs rename to src/Core/Impl/Services/ITelemetryService.cs index 9c3e084f5..a9f2163ab 100644 --- a/src/Core/Impl/Shell/ITelemetryService.cs +++ b/src/Core/Impl/Services/ITelemetryService.cs @@ -17,7 +17,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Microsoft.Python.Core.Shell { +namespace Microsoft.Python.Core.Services { public interface ITelemetryService { Task SendTelemetryAsync(TelemetryEvent telemetryEvent); } diff --git a/src/Core/Impl/Shell/IUIService.cs b/src/Core/Impl/Services/IUIService.cs similarity index 97% rename from src/Core/Impl/Shell/IUIService.cs rename to src/Core/Impl/Services/IUIService.cs index c9af48a6c..a2fc5dbeb 100644 --- a/src/Core/Impl/Shell/IUIService.cs +++ b/src/Core/Impl/Services/IUIService.cs @@ -16,7 +16,7 @@ using System.Diagnostics; using System.Threading.Tasks; -namespace Microsoft.Python.Core.Shell { +namespace Microsoft.Python.Core.Services { /// /// Service that represents the application user interface. /// diff --git a/src/Core/Impl/Services/ServiceManager.cs b/src/Core/Impl/Services/ServiceManager.cs index 5ae5af006..eeaede73b 100644 --- a/src/Core/Impl/Services/ServiceManager.cs +++ b/src/Core/Impl/Services/ServiceManager.cs @@ -22,7 +22,6 @@ using System.Runtime.CompilerServices; using Microsoft.Python.Core.Diagnostics; using Microsoft.Python.Core.Disposables; -using Microsoft.Python.Core.Shell; using static System.FormattableString; namespace Microsoft.Python.Core.Services { diff --git a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs index 30002b1d9..4211d85f3 100644 --- a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs +++ b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs @@ -20,15 +20,15 @@ using Microsoft.Python.Core; using Microsoft.Python.Core.Disposables; using Microsoft.Python.Core.Idle; +using Microsoft.Python.Core.Services; using Microsoft.Python.LanguageServer.Protocol; using Microsoft.Python.Parsing; -using StreamJsonRpc; namespace Microsoft.Python.LanguageServer.Diagnostics { internal sealed class DiagnosticsService : IDiagnosticsService, IDisposable { private readonly Dictionary> _diagnostics = new Dictionary>(); private readonly DisposableBag _disposables = DisposableBag.Create(); - private readonly JsonRpc _rpc; + private readonly IClientApplication _clientApp; private readonly object _lock = new object(); private DateTime _lastChangeTime; private bool _changed; @@ -44,7 +44,7 @@ public DiagnosticsService(IServiceContainer services) { .Add(() => idleTimeService.Idle -= OnIdle) .Add(() => idleTimeService.Idle -= OnClosing); } - _rpc = services.GetService(); + _clientApp = services.GetService(); } #region IDiagnosticsService @@ -66,12 +66,14 @@ public void Replace(Uri documentUri, IEnumerable entries) { public void Remove(Uri documentUri) { lock (_lock) { + // Before removing the document, make sure we clear its diagnostics. + _diagnostics[documentUri] = new List(); + PublishDiagnostics(); _diagnostics.Remove(documentUri); - _changed = true; } } - public int PublishingDelay { get; set; } + public int PublishingDelay { get; set; } = 1000; #endregion public void Dispose() { @@ -94,7 +96,7 @@ private void PublishDiagnostics() { uri = kvp.Key, diagnostics = kvp.Value.Select(ToDiagnostic).ToArray() }; - _rpc.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", parameters).DoNotWait(); + _clientApp.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", parameters).DoNotWait(); } _changed = false; } diff --git a/src/LanguageServer/Impl/Implementation/Server.cs b/src/LanguageServer/Impl/Implementation/Server.cs index 08d90d8fb..7dc9e6782 100644 --- a/src/LanguageServer/Impl/Implementation/Server.cs +++ b/src/LanguageServer/Impl/Implementation/Server.cs @@ -27,7 +27,7 @@ using Microsoft.Python.Core.Disposables; using Microsoft.Python.Core.IO; using Microsoft.Python.Core.Logging; -using Microsoft.Python.Core.Shell; +using Microsoft.Python.Core.Services; using Microsoft.Python.LanguageServer.Completion; using Microsoft.Python.LanguageServer.Diagnostics; using Microsoft.Python.LanguageServer.Protocol; diff --git a/src/LanguageServer/Impl/LanguageServer.cs b/src/LanguageServer/Impl/LanguageServer.cs index d856906c4..bf1cc15a8 100644 --- a/src/LanguageServer/Impl/LanguageServer.cs +++ b/src/LanguageServer/Impl/LanguageServer.cs @@ -25,7 +25,7 @@ using Microsoft.Python.Core.Disposables; using Microsoft.Python.Core.Idle; using Microsoft.Python.Core.Logging; -using Microsoft.Python.Core.Shell; +using Microsoft.Python.Core.Services; using Microsoft.Python.Core.Text; using Microsoft.Python.Core.Threading; using Microsoft.Python.LanguageServer.Extensibility; diff --git a/src/LanguageServer/Impl/Program.cs b/src/LanguageServer/Impl/Program.cs index 2c91268fa..6532eef47 100644 --- a/src/LanguageServer/Impl/Program.cs +++ b/src/LanguageServer/Impl/Program.cs @@ -46,14 +46,15 @@ public static void Main(string[] args) { using (var rpc = new LanguageServerJsonRpc(cout, cin, messageFormatter, server)) { rpc.TraceSource.Switch.Level = SourceLevels.Error; rpc.SynchronizationContext = new SingleThreadSynchronizationContext(); + var clientApp = new ClientApplication(rpc); var osp = new OSPlatform(); services - .AddService(rpc) - .AddService(new Logger(rpc)) - .AddService(new UIService(rpc)) - .AddService(new ProgressService(rpc)) - .AddService(new TelemetryService(rpc)) + .AddService(clientApp) + .AddService(new Logger(clientApp)) + .AddService(new UIService(clientApp)) + .AddService(new ProgressService(clientApp)) + .AddService(new TelemetryService(clientApp)) .AddService(new IdleTimeService()) .AddService(osp) .AddService(new FileSystem(osp)); diff --git a/src/LanguageServer/Impl/Services/ClientApplication.cs b/src/LanguageServer/Impl/Services/ClientApplication.cs new file mode 100644 index 000000000..6c953b170 --- /dev/null +++ b/src/LanguageServer/Impl/Services/ClientApplication.cs @@ -0,0 +1,38 @@ +// 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; +using System.Threading.Tasks; +using Microsoft.Python.Core.Services; +using StreamJsonRpc; + +namespace Microsoft.Python.LanguageServer.Services { + internal sealed class ClientApplication : IClientApplication { + private readonly JsonRpc _rpc; + + public ClientApplication(JsonRpc rpc) { + _rpc = rpc; + } + + public Task NotifyAsync(string targetName, params object[] arguments) + => _rpc.NotifyAsync(targetName, arguments); + + public Task NotifyWithParameterObjectAsync(string targetName, object argument = null) + => _rpc.NotifyAsync(targetName, argument); + + public Task InvokeWithParameterObjectAsync(string targetName, object argument = null, CancellationToken cancellationToken = default) + => _rpc.InvokeWithParameterObjectAsync(targetName, argument, cancellationToken); + } +} diff --git a/src/LanguageServer/Impl/Services/Logger.cs b/src/LanguageServer/Impl/Services/Logger.cs index 2a01cf385..bbe25ea19 100644 --- a/src/LanguageServer/Impl/Services/Logger.cs +++ b/src/LanguageServer/Impl/Services/Logger.cs @@ -19,15 +19,15 @@ using System.Threading.Tasks; using Microsoft.Python.Core; using Microsoft.Python.Core.Logging; +using Microsoft.Python.Core.Services; using Microsoft.Python.LanguageServer.Protocol; -using StreamJsonRpc; namespace Microsoft.Python.LanguageServer.Services { internal sealed class Logger: ILogger { - private readonly JsonRpc _rpc; + private readonly IClientApplication _clientApp; - public Logger(JsonRpc rpc) { - _rpc = rpc; + public Logger(IClientApplication clientApp) { + _clientApp = clientApp; } public TraceEventType LogLevel { get; set; } = TraceEventType.Error; @@ -54,7 +54,7 @@ public Task LogMessageAsync(string message, TraceEventType eventType) { type = eventType.ToMessageType(), message = message }; - return _rpc.NotifyWithParameterObjectAsync("window/logMessage", parameters); + return _clientApp.NotifyWithParameterObjectAsync("window/logMessage", parameters); } } } diff --git a/src/LanguageServer/Impl/Services/ProgressService.cs b/src/LanguageServer/Impl/Services/ProgressService.cs index 1443913dc..6bbb1c00b 100644 --- a/src/LanguageServer/Impl/Services/ProgressService.cs +++ b/src/LanguageServer/Impl/Services/ProgressService.cs @@ -16,26 +16,25 @@ using System.Threading.Tasks; using Microsoft.Python.Core; -using Microsoft.Python.Core.Shell; -using StreamJsonRpc; +using Microsoft.Python.Core.Services; namespace Microsoft.Python.LanguageServer.Services { public sealed class ProgressService : IProgressService { - private readonly JsonRpc _rpc; - public ProgressService(JsonRpc rpc) { - _rpc = rpc; + private readonly IClientApplication _clientApp; + public ProgressService(IClientApplication clientApp) { + _clientApp = clientApp; } - public IProgress BeginProgress() => new Progress(_rpc); + public IProgress BeginProgress() => new Progress(_clientApp); private class Progress : IProgress { - private readonly JsonRpc _rpc; - public Progress(JsonRpc rpc) { - _rpc = rpc; - _rpc.NotifyAsync("python/beginProgress").DoNotWait(); + private readonly IClientApplication _clientApp; + public Progress(IClientApplication clientApp) { + _clientApp = clientApp; + _clientApp.NotifyAsync("python/beginProgress").DoNotWait(); } - public Task Report(string message) => _rpc.NotifyAsync("python/reportProgress", message); - public void Dispose() => _rpc.NotifyAsync("python/endProgress").DoNotWait(); + public Task Report(string message) => _clientApp.NotifyAsync("python/reportProgress", message); + public void Dispose() => _clientApp.NotifyAsync("python/endProgress").DoNotWait(); } } } diff --git a/src/LanguageServer/Impl/Services/TelemetryService.cs b/src/LanguageServer/Impl/Services/TelemetryService.cs index 353887179..c7c1df874 100644 --- a/src/LanguageServer/Impl/Services/TelemetryService.cs +++ b/src/LanguageServer/Impl/Services/TelemetryService.cs @@ -16,18 +16,17 @@ using System.Reflection; using System.Threading.Tasks; -using Microsoft.Python.Core.Shell; -using StreamJsonRpc; +using Microsoft.Python.Core.Services; namespace Microsoft.Python.LanguageServer.Services { #pragma warning disable CS0612 // Type or member is obsolete public sealed class TelemetryService : ITelemetryService { #pragma warning restore CS0612 // Type or member is obsolete - private readonly JsonRpc _rpc; + private readonly IClientApplication _clientApp; private readonly string _plsVersion; - public TelemetryService(JsonRpc rpc) { - _rpc = rpc; + public TelemetryService(IClientApplication clientApp) { + _clientApp = clientApp; _plsVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); #if DEBUG @@ -37,7 +36,7 @@ public TelemetryService(JsonRpc rpc) { public Task SendTelemetryAsync(TelemetryEvent telemetryEvent) { telemetryEvent.Properties["plsVersion"] = _plsVersion; - return _rpc.NotifyWithParameterObjectAsync("telemetry/event", telemetryEvent); + return _clientApp.NotifyWithParameterObjectAsync("telemetry/event", telemetryEvent); } } } diff --git a/src/LanguageServer/Impl/Services/UIService.cs b/src/LanguageServer/Impl/Services/UIService.cs index e2199869b..b1197f9a4 100644 --- a/src/LanguageServer/Impl/Services/UIService.cs +++ b/src/LanguageServer/Impl/Services/UIService.cs @@ -16,16 +16,15 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using Microsoft.Python.Core.Shell; +using Microsoft.Python.Core.Services; using Microsoft.Python.LanguageServer.Protocol; -using StreamJsonRpc; namespace Microsoft.Python.LanguageServer.Services { public sealed class UIService : IUIService { - private readonly JsonRpc _rpc; + private readonly IClientApplication _clientApp; - public UIService(JsonRpc rpc) { - _rpc = rpc; + public UIService(IClientApplication clientApp) { + _clientApp = clientApp; } public Task ShowMessageAsync(string message, TraceEventType eventType) { @@ -33,7 +32,7 @@ public Task ShowMessageAsync(string message, TraceEventType eventType) { type = eventType.ToMessageType(), message = message }; - return _rpc.NotifyWithParameterObjectAsync("window/showMessage", parameters); + return _clientApp.NotifyWithParameterObjectAsync("window/showMessage", parameters); } public async Task ShowMessageAsync(string message, string[] actions, TraceEventType eventType) { @@ -42,11 +41,11 @@ public async Task ShowMessageAsync(string message, string[] actions, Tra message = message, actions = actions.Select(a => new MessageActionItem { title = a }).ToArray() }; - var result = await _rpc.InvokeWithParameterObjectAsync("window/showMessageRequest", parameters); + var result = await _clientApp.InvokeWithParameterObjectAsync("window/showMessageRequest", parameters); return result?.title; } public Task SetStatusBarMessageAsync(string message) - => _rpc.NotifyWithParameterObjectAsync("window/setStatusBarMessage", message); + => _clientApp.NotifyWithParameterObjectAsync("window/setStatusBarMessage", message); } } diff --git a/src/LanguageServer/Impl/Telemetry.cs b/src/LanguageServer/Impl/Telemetry.cs index 14eb3378a..875a1aa04 100644 --- a/src/LanguageServer/Impl/Telemetry.cs +++ b/src/LanguageServer/Impl/Telemetry.cs @@ -18,7 +18,7 @@ using System.Diagnostics; using System.Reflection; using Microsoft.Python.Core; -using Microsoft.Python.Core.Shell; +using Microsoft.Python.Core.Services; using Microsoft.Python.LanguageServer.Protocol; using StreamJsonRpc; @@ -105,7 +105,7 @@ public override void Write(string message) { } public override void WriteLine(string message) { } // The only thing that this listener should do is look for RPC - // incovation error events to then send. The base TraceListener + // invocation error events to then send. The base TraceListener // implements the its methods by building strings from given // arguments, then passing them to the abstract Write and // WriteLine (implemented as noops above). To prevent that extra diff --git a/src/LanguageServer/Test/DiagnosticsTests.cs b/src/LanguageServer/Test/DiagnosticsTests.cs index 3a5eb2d7d..27b10eee9 100644 --- a/src/LanguageServer/Test/DiagnosticsTests.cs +++ b/src/LanguageServer/Test/DiagnosticsTests.cs @@ -13,13 +13,18 @@ // See the Apache Version 2.0 License for specific language governing // permissions and limitations under the License. +using System; using System.Threading.Tasks; using FluentAssertions; using Microsoft.Python.Analysis.Diagnostics; using Microsoft.Python.Analysis.Documents; +using Microsoft.Python.Core; +using Microsoft.Python.Core.Idle; +using Microsoft.Python.Core.Services; using Microsoft.Python.Core.Text; -using Microsoft.Python.LanguageServer.Sources; +using Microsoft.Python.LanguageServer.Protocol; using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; using TestUtilities; namespace Microsoft.Python.LanguageServer.Tests { @@ -39,12 +44,12 @@ public async Task BasicChange() { const string code = @"x = "; var analysis = await GetAnalysisAsync(code); - var ds = _sm.GetService(); + var ds = Services.GetService(); var doc = analysis.Document; ds.Diagnostics[doc.Uri].Count.Should().Be(1); - doc.Update(new [] {new DocumentChange { + doc.Update(new[] {new DocumentChange { InsertedText = "1", ReplacedSpan = new SourceSpan(1, 5, 1, 5) } }); @@ -61,17 +66,97 @@ public async Task BasicChange() { ds.Diagnostics[doc.Uri].Count.Should().Be(1); } + [TestMethod, Priority(0)] + public async Task TwoDocuments() { + const string code1 = @"x = "; + const string code2 = @"y = "; + + var analysis1 = await GetAnalysisAsync(code1); + var analysis2 = await GetNextAnalysisAsync(code2); + var ds = Services.GetService(); + + var doc1 = analysis1.Document; + var doc2 = analysis2.Document; + + ds.Diagnostics[doc1.Uri].Count.Should().Be(1); + ds.Diagnostics[doc2.Uri].Count.Should().Be(1); + + doc2.Update(new[] {new DocumentChange { + InsertedText = "1", + ReplacedSpan = new SourceSpan(1, 5, 1, 5) + } }); + + await doc2.GetAnalysisAsync(); + ds.Diagnostics[doc1.Uri].Count.Should().Be(1); + ds.Diagnostics[doc2.Uri].Count.Should().Be(0); + + doc2.Update(new[] {new DocumentChange { + InsertedText = string.Empty, + ReplacedSpan = new SourceSpan(1, 5, 1, 6) + } }); + + await doc2.GetAnalysisAsync(); + ds.Diagnostics[doc2.Uri].Count.Should().Be(1); + + doc1.Dispose(); + ds.Diagnostics[doc2.Uri].Count.Should().Be(1); + ds.Diagnostics.TryGetValue(doc1.Uri, out _).Should().BeFalse(); + } + + [TestMethod, Priority(0)] + public async Task Publish() { + const string code = @"x = "; + + var analysis = await GetAnalysisAsync(code); + var doc = analysis.Document; + + var ds = Services.GetService(); + var clientApp = Services.GetService(); + var idle = Services.GetService(); + + var expected = 1; + clientApp.When(x => x.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", Arg.Any())) + .Do(x => { + var dp = x.Args()[1] as PublishDiagnosticsParams; + dp.Should().NotBeNull(); + dp.diagnostics.Length.Should().Be(expected); + dp.uri.Should().Be(doc.Uri); + }); + idle.Idle += Raise.EventWith(null, EventArgs.Empty); + + expected = 0; + doc.Update(new[] {new DocumentChange { + InsertedText = "1", + ReplacedSpan = new SourceSpan(1, 5, 1, 5) + } }); + + await doc.GetAnalysisAsync(); + idle.Idle += Raise.EventWith(null, EventArgs.Empty); + } + [TestMethod, Priority(0)] public async Task CloseDocument() { const string code = @"x = "; var analysis = await GetAnalysisAsync(code); - var ds = _sm.GetService(); + var ds = Services.GetService(); var doc = analysis.Document; ds.Diagnostics[doc.Uri].Count.Should().Be(1); - doc.Dispose(); + var clientApp = Services.GetService(); + var idle = Services.GetService(); + var uri = doc.Uri; + clientApp.When(x => x.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", Arg.Any())) + .Do(x => { + var dp = x.Args()[1] as PublishDiagnosticsParams; + dp.Should().NotBeNull(); + dp.diagnostics.Length.Should().Be(0); + dp.uri.Should().Be(uri); + }); + + doc.Dispose(); + idle.Idle += Raise.EventWith(null, EventArgs.Empty); ds.Diagnostics.TryGetValue(doc.Uri, out _).Should().BeFalse(); } } diff --git a/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj b/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj index f1a3b09f0..20a662dbb 100644 --- a/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj +++ b/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj @@ -31,6 +31,7 @@ all runtime; build; native; contentfiles; analyzers + From 08df23bb9761d900a047519bc64e36ff471bb718 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 7 Feb 2019 12:55:21 -0800 Subject: [PATCH 06/11] Unused var --- src/LanguageServer/Test/DiagnosticsTests.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/LanguageServer/Test/DiagnosticsTests.cs b/src/LanguageServer/Test/DiagnosticsTests.cs index 27b10eee9..6b97c5e4f 100644 --- a/src/LanguageServer/Test/DiagnosticsTests.cs +++ b/src/LanguageServer/Test/DiagnosticsTests.cs @@ -18,7 +18,6 @@ using FluentAssertions; using Microsoft.Python.Analysis.Diagnostics; using Microsoft.Python.Analysis.Documents; -using Microsoft.Python.Core; using Microsoft.Python.Core.Idle; using Microsoft.Python.Core.Services; using Microsoft.Python.Core.Text; @@ -110,7 +109,6 @@ public async Task Publish() { var analysis = await GetAnalysisAsync(code); var doc = analysis.Document; - var ds = Services.GetService(); var clientApp = Services.GetService(); var idle = Services.GetService(); From f8f51ae0b755cb42e8a94a2a2d59261ab5a0e945 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 7 Feb 2019 12:57:10 -0800 Subject: [PATCH 07/11] Test forced publish on close --- src/LanguageServer/Test/DiagnosticsTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/LanguageServer/Test/DiagnosticsTests.cs b/src/LanguageServer/Test/DiagnosticsTests.cs index 6b97c5e4f..8a03b8f35 100644 --- a/src/LanguageServer/Test/DiagnosticsTests.cs +++ b/src/LanguageServer/Test/DiagnosticsTests.cs @@ -143,19 +143,20 @@ public async Task CloseDocument() { ds.Diagnostics[doc.Uri].Count.Should().Be(1); var clientApp = Services.GetService(); - var idle = Services.GetService(); var uri = doc.Uri; + var callReceived = false; clientApp.When(x => x.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", Arg.Any())) .Do(x => { var dp = x.Args()[1] as PublishDiagnosticsParams; dp.Should().NotBeNull(); dp.diagnostics.Length.Should().Be(0); dp.uri.Should().Be(uri); + callReceived = true; }); doc.Dispose(); - idle.Idle += Raise.EventWith(null, EventArgs.Empty); ds.Diagnostics.TryGetValue(doc.Uri, out _).Should().BeFalse(); + callReceived.Should().BeTrue(); } } } From 7370e2dd7a53fc481bfd0bcb0f80eea08cb4f26a Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 7 Feb 2019 13:07:22 -0800 Subject: [PATCH 08/11] Fix typo --- src/LanguageServer/Impl/Services/ClientApplication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LanguageServer/Impl/Services/ClientApplication.cs b/src/LanguageServer/Impl/Services/ClientApplication.cs index 6c953b170..6aebe72f9 100644 --- a/src/LanguageServer/Impl/Services/ClientApplication.cs +++ b/src/LanguageServer/Impl/Services/ClientApplication.cs @@ -30,7 +30,7 @@ public Task NotifyAsync(string targetName, params object[] arguments) => _rpc.NotifyAsync(targetName, arguments); public Task NotifyWithParameterObjectAsync(string targetName, object argument = null) - => _rpc.NotifyAsync(targetName, argument); + => _rpc.NotifyWithParameterObjectAsync(targetName, argument); public Task InvokeWithParameterObjectAsync(string targetName, object argument = null, CancellationToken cancellationToken = default) => _rpc.InvokeWithParameterObjectAsync(targetName, argument, cancellationToken); From a728657a5d80411a5eb8452c8f1aa5d20893462a Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 7 Feb 2019 13:28:53 -0800 Subject: [PATCH 09/11] Update test framework --- .../Ast/Test/Microsoft.Python.Analysis.Tests.csproj | 7 ++++--- .../Test/Microsoft.Python.LanguageServer.Tests.csproj | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj b/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj index d9a827933..21bcf5961 100644 --- a/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj +++ b/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj @@ -23,14 +23,15 @@ - + - - + + all runtime; build; native; contentfiles; analyzers + diff --git a/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj b/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj index 20a662dbb..66de5aa0c 100644 --- a/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj +++ b/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj @@ -23,10 +23,10 @@ - + - - + + all runtime; build; native; contentfiles; analyzers From 77aa79b38e82b8a3d2060a5505e02c87d4d75ad0 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Thu, 7 Feb 2019 19:36:58 -0800 Subject: [PATCH 10/11] Remove incorrect reference --- src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj b/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj index 21bcf5961..8e975d378 100644 --- a/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj +++ b/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj @@ -42,11 +42,6 @@ - - - ..\..\..\..\..\Users\mikhaila\.nuget\packages\nsubstitute\4.0.0\lib\netstandard2.0\NSubstitute.dll - - From 2efd0d8fb2adee814924820481014f587fe501e4 Mon Sep 17 00:00:00 2001 From: Mikhail Arkhipov Date: Fri, 8 Feb 2019 19:19:38 -0800 Subject: [PATCH 11/11] Move interface to the main class part --- .../Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs | 2 +- src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs index c62d9f077..74c1ef655 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs @@ -25,7 +25,7 @@ namespace Microsoft.Python.Analysis.Analyzer.Evaluation { /// Helper class that provides methods for looking up variables /// and types in a chain of scopes during analysis. /// - internal sealed partial class ExpressionEval : IExpressionEvaluator { + internal sealed partial class ExpressionEval { public IPythonType GetTypeFromPepHint(Node node) { var location = GetLoc(node); var content = (Module as IDocument)?.Content; diff --git a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs index a78c75dbd..df6995e4b 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs @@ -33,7 +33,7 @@ namespace Microsoft.Python.Analysis.Analyzer.Evaluation { /// Helper class that provides methods for looking up variables /// and types in a chain of scopes during analysis. /// - internal sealed partial class ExpressionEval { + internal sealed partial class ExpressionEval : IExpressionEvaluator { private readonly Stack _openScopes = new Stack(); private readonly List _diagnostics = new List(); private readonly object _lock = new object();