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.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 9e4ba96c1..df6995e4b 100644 --- a/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs +++ b/src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs @@ -35,7 +35,8 @@ namespace Microsoft.Python.Analysis.Analyzer.Evaluation { /// internal sealed partial class ExpressionEval : IExpressionEvaluator { 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..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 { @@ -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/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/Impl/Diagnostics/IDiagnosticsService.cs b/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs index 133e2fc55..7de168b73 100644 --- a/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs +++ b/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs @@ -18,8 +18,25 @@ namespace Microsoft.Python.Analysis.Diagnostics { public interface IDiagnosticsService { - IReadOnlyList Diagnostics { get; } - void Add(Uri documentUri, DiagnosticsEntry entry); + /// + /// 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 f0d0411c7..000000000 --- a/src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs +++ /dev/null @@ -1,26 +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 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)); - } -} diff --git a/src/Analysis/Ast/Impl/Modules/PythonModule.cs b/src/Analysis/Ast/Impl/Modules/PythonModule.cs index 05192c8c5..c0f391e9e 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) : @@ -218,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(); } @@ -250,6 +252,7 @@ private void LoadContent(string content) { public void Dispose() => Dispose(true); protected virtual void Dispose(bool disposing) { + _diagnosticsService?.Remove(Uri); _allProcessingCts.Cancel(); _allProcessingCts.Dispose(); } @@ -288,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(); @@ -314,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(); @@ -352,7 +353,7 @@ private void Parse() { } private void Parse(CancellationToken cancellationToken) { - var sink = new CollectingErrorSink(); + CollectingErrorSink sink = null; int version; Parser parser; @@ -360,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(); @@ -376,7 +381,13 @@ private void Parse(CancellationToken cancellationToken) { throw new OperationCanceledException(); } _ast = ast; - _parseErrors = sink.Diagnostics; + _parseErrors = sink?.Diagnostics ?? Array.Empty(); + + // Do not report issues with libraries or stubs + if (sink != null) { + _diagnosticsService?.Replace(Uri, _parseErrors); + } + _parsingTask = null; ContentState = State.Parsed; } @@ -428,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}"); } } @@ -448,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; @@ -477,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 51682bc1d..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,22 +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 Services { get; private set; } + + protected virtual IDiagnosticsService GetDiagnosticsService(IServiceContainer s) => null; protected virtual ServiceManager CreateServiceManager() { - var 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")); @@ -65,7 +70,16 @@ protected async Task CreateServicesAsync(string root, Interpret var sm = CreateServiceManager(); - sm.AddService(new DiagnosticsService()); + var clientApp = Substitute.For(); + sm.AddService(clientApp); + + var idle = Substitute.For(); + sm.AddService(idle); + + var ds = GetDiagnosticsService(Services); + if (ds != null) { + sm.AddService(ds); + } TestLogger.Log(TraceEventType.Information, "Create TestDependencyResolver"); var dependencyResolver = new TestDependencyResolver(); @@ -94,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); @@ -103,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; @@ -145,29 +164,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 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/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj b/src/Analysis/Ast/Test/Microsoft.Python.Analysis.Tests.csproj index 345a3ce1d..8e975d378 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/Analysis/Ast/Impl/Diagnostics/IDiagnosticsPublisher.cs b/src/Core/Impl/Services/IClientApplication.cs similarity index 57% rename from src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsPublisher.cs rename to src/Core/Impl/Services/IClientApplication.cs index 7f428e975..00a52a713 100644 --- a/src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsPublisher.cs +++ b/src/Core/Impl/Services/IClientApplication.cs @@ -13,11 +13,16 @@ // 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; +using System.Threading; +using System.Threading.Tasks; -namespace Microsoft.Python.Analysis.Diagnostics { - public interface IDiagnosticsPublisher { - void AddDiagnostics(string message, SourceSpan span, int errorCode, Severity severity); +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 77fadecaf..4211d85f3 100644 --- a/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs +++ b/src/LanguageServer/Impl/Diagnostics/DiagnosticsService.cs @@ -20,81 +20,96 @@ 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> _pendingDiagnostics = new Dictionary>(); + 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; 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); + } + _clientApp = services.GetService(); } #region IDiagnosticsService - public IReadOnlyList Diagnostics { + public IReadOnlyDictionary> Diagnostics { get { - lock(_lock) { - return _pendingDiagnostics.Values.SelectMany().ToArray(); + lock (_lock) { + return _diagnostics.ToDictionary(kvp => kvp.Key, kvp => kvp.Value as IReadOnlyList); } } } - public void Add(Uri documentUri, DiagnosticsEntry entry) { - lock(_lock) { - if(!_pendingDiagnostics.TryGetValue(documentUri, out var list)) { - _pendingDiagnostics[documentUri] = list = new List(); - } - list.Add(entry); + public void Replace(Uri documentUri, IEnumerable entries) { + lock (_lock) { + _diagnostics[documentUri] = entries.ToList(); _lastChangeTime = DateTime.Now; + _changed = true; } } - public int PublishingDelay { get; set; } + 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); + } + } + + public int PublishingDelay { get; set; } = 1000; #endregion - public void Dispose() => _disposables.TryDispose(); + public void Dispose() { + _disposables.TryDispose(); + ClearAllDiagnostics(); + } private void OnClosing(object sender, EventArgs e) => Dispose(); private void OnIdle(object sender, EventArgs e) { - if (_pendingDiagnostics.Count > 0 && (DateTime.Now - _lastChangeTime).TotalMilliseconds > PublishingDelay) { - PublishPendingDiagnostics(); + if (_changed && (DateTime.Now - _lastChangeTime).TotalMilliseconds > PublishingDelay) { + PublishDiagnostics(); } } - private void PublishPendingDiagnostics() { - List>> list; - + private void PublishDiagnostics() { lock (_lock) { - list = _pendingDiagnostics.ToList(); - _pendingDiagnostics.Clear(); + foreach (var kvp in _diagnostics) { + var parameters = new PublishDiagnosticsParams { + uri = kvp.Key, + diagnostics = kvp.Value.Select(ToDiagnostic).ToArray() + }; + _clientApp.NotifyWithParameterObjectAsync("textDocument/publishDiagnostics", parameters).DoNotWait(); + } + _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 ClearAllDiagnostics() { + lock (_lock) { + _diagnostics.Clear(); + _changed = false; } } - 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/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..6aebe72f9 --- /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.NotifyWithParameterObjectAsync(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 new file mode 100644 index 000000000..8a03b8f35 --- /dev/null +++ b/src/LanguageServer/Test/DiagnosticsTests.cs @@ -0,0 +1,162 @@ +// Copyright(c) Microsoft Corporation +// All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the License); you may not use +// this file except in compliance with the License. You may obtain a copy of the +// License at http://www.apache.org/licenses/LICENSE-2.0 +// +// THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY +// IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +// MERCHANTABILITY OR NON-INFRINGEMENT. +// +// See the Apache Version 2.0 License for specific language governing +// permissions and limitations under the License. + +using System; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Python.Analysis.Diagnostics; +using Microsoft.Python.Analysis.Documents; +using Microsoft.Python.Core.Idle; +using Microsoft.Python.Core.Services; +using Microsoft.Python.Core.Text; +using Microsoft.Python.LanguageServer.Protocol; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using NSubstitute; +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 = Services.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 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 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 = Services.GetService(); + + var doc = analysis.Document; + ds.Diagnostics[doc.Uri].Count.Should().Be(1); + + var clientApp = 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(); + ds.Diagnostics.TryGetValue(doc.Uri, out _).Should().BeFalse(); + callReceived.Should().BeTrue(); + } + } +} 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); } } diff --git a/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj b/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj index f1a3b09f0..66de5aa0c 100644 --- a/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj +++ b/src/LanguageServer/Test/Microsoft.Python.LanguageServer.Tests.csproj @@ -23,14 +23,15 @@ - + - - + + all runtime; build; native; contentfiles; analyzers +