Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit d25e0dd

Browse files
author
Mikhail Arkhipov
authored
Output syntax errors from the parser (#584)
* Fix #470 * Output syntax errors * Properly clear * - Fix async issue with analysis completion - Clean up diagnostics service interface - Use real DS in tests * Add publishing test Add NSubstitute Move all services to the same namespace. * Unused var * Test forced publish on close * Fix typo * Update test framework * Remove incorrect reference * Move interface to the main class part
1 parent b906ad8 commit d25e0dd

30 files changed

+417
-186
lines changed

src/Analysis/Ast/Impl/Analyzer/Definitions/IExpressionEvaluator.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
// permissions and limitations under the License.
1515

1616
using System;
17+
using System.Collections.Generic;
1718
using System.Threading;
1819
using System.Threading.Tasks;
20+
using Microsoft.Python.Analysis.Diagnostics;
1921
using Microsoft.Python.Analysis.Types;
2022
using Microsoft.Python.Analysis.Values;
2123
using Microsoft.Python.Core;
@@ -59,12 +61,12 @@ public interface IExpressionEvaluator {
5961

6062
IMember LookupNameInScopes(string name, out IScope scope);
6163

62-
IPythonType GetTypeFromPepHint(Node node);
6364
IPythonType GetTypeFromString(string typeString);
6465

6566
PythonAst Ast { get; }
6667
IPythonModule Module { get; }
6768
IPythonInterpreter Interpreter { get; }
6869
IServiceContainer Services { get; }
70+
IEnumerable<DiagnosticsEntry> Diagnostics { get; }
6971
}
7072
}

src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.Hints.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ namespace Microsoft.Python.Analysis.Analyzer.Evaluation {
2525
/// Helper class that provides methods for looking up variables
2626
/// and types in a chain of scopes during analysis.
2727
/// </summary>
28-
internal sealed partial class ExpressionEval : IExpressionEvaluator {
28+
internal sealed partial class ExpressionEval {
2929
public IPythonType GetTypeFromPepHint(Node node) {
3030
var location = GetLoc(node);
3131
var content = (Module as IDocument)?.Content;

src/Analysis/Ast/Impl/Analyzer/Evaluation/ExpressionEval.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ namespace Microsoft.Python.Analysis.Analyzer.Evaluation {
3535
/// </summary>
3636
internal sealed partial class ExpressionEval : IExpressionEvaluator {
3737
private readonly Stack<Scope> _openScopes = new Stack<Scope>();
38-
private readonly IDiagnosticsService _diagnostics;
38+
private readonly List<DiagnosticsEntry> _diagnostics = new List<DiagnosticsEntry>();
39+
private readonly object _lock = new object();
3940

4041
public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAst ast) {
4142
Services = services ?? throw new ArgumentNullException(nameof(services));
@@ -47,7 +48,6 @@ public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAs
4748
DefaultLookupOptions = LookupOptions.Normal;
4849

4950
//Log = services.GetService<ILogger>();
50-
_diagnostics = services.GetService<IDiagnosticsService>();
5151
}
5252

5353
public LookupOptions DefaultLookupOptions { get; set; }
@@ -69,6 +69,7 @@ public ExpressionEval(IServiceContainer services, IPythonModule module, PythonAs
6969
IScope IExpressionEvaluator.CurrentScope => CurrentScope;
7070
IGlobalScope IExpressionEvaluator.GlobalScope => GlobalScope;
7171
public LocationInfo GetLocation(Node node) => node?.GetLocation(Module, Ast) ?? LocationInfo.Empty;
72+
public IEnumerable<DiagnosticsEntry> Diagnostics => _diagnostics;
7273

7374
public Task<IMember> GetValueFromExpressionAsync(Expression expr, CancellationToken cancellationToken = default)
7475
=> GetValueFromExpressionAsync(expr, DefaultLookupOptions, cancellationToken);
@@ -228,11 +229,11 @@ private async Task<IMember> GetValueFromConditionalAsync(ConditionalExpression e
228229
return trueValue ?? falseValue;
229230
}
230231

231-
private void AddDiagnostics(Uri documentUri, IEnumerable<DiagnosticsEntry> entries) {
232+
private void ReportDiagnostics(Uri documentUri, IEnumerable<DiagnosticsEntry> entries) {
232233
// Do not add if module is library, etc. Only handle user code.
233234
if (Module.ModuleType == ModuleType.User) {
234-
foreach (var e in entries) {
235-
_diagnostics?.Add(documentUri, e);
235+
lock (_lock) {
236+
_diagnostics.AddRange(entries);
236237
}
237238
}
238239
}

src/Analysis/Ast/Impl/Analyzer/PythonAnalyzer.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
using Microsoft.Python.Core;
2323
using Microsoft.Python.Core.Diagnostics;
2424
using Microsoft.Python.Core.Logging;
25-
using Microsoft.Python.Core.Shell;
25+
using Microsoft.Python.Core.Services;
2626

2727
namespace Microsoft.Python.Analysis.Analyzer {
2828
public sealed class PythonAnalyzer : IPythonAnalyzer, IDisposable {
@@ -55,7 +55,6 @@ public PythonAnalyzer(IServiceManager services, string root) {
5555
public async Task AnalyzeDocumentAsync(IDocument document, CancellationToken cancellationToken) {
5656
var node = new DependencyChainNode(document);
5757
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(_globalCts.Token, cancellationToken)) {
58-
node.Analyzable.NotifyAnalysisPending();
5958
try {
6059
var analysis = await AnalyzeAsync(node, cts.Token);
6160
node.Analyzable.NotifyAnalysisComplete(analysis);
@@ -78,17 +77,21 @@ public async Task AnalyzeDocumentDependencyChainAsync(IDocument document, Cancel
7877
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(_globalCts.Token, cancellationToken)) {
7978
var dependencyRoot = await _dependencyResolver.GetDependencyChainAsync(document, cts.Token);
8079
// Notify each dependency that the analysis is now pending
81-
NotifyAnalysisPending(dependencyRoot);
80+
NotifyAnalysisPending(document, dependencyRoot);
8281

8382
cts.Token.ThrowIfCancellationRequested();
8483
await AnalyzeChainAsync(dependencyRoot, cts.Token);
8584
}
8685
}
8786

88-
private void NotifyAnalysisPending(IDependencyChainNode node) {
89-
node.Analyzable.NotifyAnalysisPending();
87+
private void NotifyAnalysisPending(IDocument document, IDependencyChainNode node) {
88+
// Notify each dependency that the analysis is now pending except the source
89+
// since if document has changed, it already incremented its expected analysis.
90+
if (node.Analyzable != document) {
91+
node.Analyzable.NotifyAnalysisPending();
92+
}
9093
foreach (var c in node.Children) {
91-
NotifyAnalysisPending(c);
94+
NotifyAnalysisPending(document, c);
9295
}
9396
}
9497

src/Analysis/Ast/Impl/Analyzer/PythonInterpreter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
using Microsoft.Python.Analysis.Specializations.Typing;
2424
using Microsoft.Python.Analysis.Types;
2525
using Microsoft.Python.Core;
26-
using Microsoft.Python.Core.Shell;
26+
using Microsoft.Python.Core.Services;
2727
using Microsoft.Python.Parsing;
2828

2929
namespace Microsoft.Python.Analysis.Analyzer {

src/Analysis/Ast/Impl/Diagnostics/IDiagnosticsService.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,25 @@
1818

1919
namespace Microsoft.Python.Analysis.Diagnostics {
2020
public interface IDiagnosticsService {
21-
IReadOnlyList<DiagnosticsEntry> Diagnostics { get; }
22-
void Add(Uri documentUri, DiagnosticsEntry entry);
21+
/// <summary>
22+
/// Current complete diagnostics.
23+
/// </summary>
24+
IReadOnlyDictionary<Uri, IReadOnlyList<DiagnosticsEntry>> Diagnostics { get; }
25+
26+
/// <summary>
27+
/// Replaces diagnostics for the document by the new set.
28+
/// </summary>
29+
void Replace(Uri documentUri, IEnumerable<DiagnosticsEntry> entries);
30+
31+
/// <summary>
32+
/// Removes document from the diagnostics report. Typically when document closes.
33+
/// </summary>
34+
void Remove(Uri documentUri);
35+
36+
/// <summary>
37+
/// Defines delay in milliseconds from the idle time start and
38+
/// the diagnostic publishing to the client.
39+
/// </summary>
2340
int PublishingDelay { get; set; }
2441
}
2542
}

src/Analysis/Ast/Impl/Extensions/DiagnosticsServiceExtensions.cs

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/Analysis/Ast/Impl/Modules/PythonModule.cs

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ protected enum State {
5656
private readonly DocumentBuffer _buffer = new DocumentBuffer();
5757
private readonly CancellationTokenSource _allProcessingCts = new CancellationTokenSource();
5858
private IReadOnlyList<DiagnosticsEntry> _parseErrors = Array.Empty<DiagnosticsEntry>();
59+
private readonly IDiagnosticsService _diagnosticsService;
5960

6061
private string _documentation; // Must be null initially.
6162
private TaskCompletionSource<IDocumentAnalysis> _analysisTcs;
@@ -72,14 +73,15 @@ protected enum State {
7273
protected State ContentState { get; set; } = State.None;
7374

7475
protected PythonModule(string name, ModuleType moduleType, IServiceContainer services) {
75-
Check.ArgumentNotNull(nameof(name), name);
76-
Name = name;
77-
Services = services;
76+
Name = name ?? throw new ArgumentNullException(nameof(name));
77+
Services = services ?? throw new ArgumentNullException(nameof(services));
7878
ModuleType = moduleType;
7979

8080
Log = services?.GetService<ILogger>();
8181
Interpreter = services?.GetService<IPythonInterpreter>();
8282
Analysis = new EmptyAnalysis(services, this);
83+
84+
_diagnosticsService = services.GetService<IDiagnosticsService>();
8385
}
8486

8587
protected PythonModule(string moduleName, string filePath, ModuleType moduleType, IPythonModule stub, IServiceContainer services) :
@@ -218,13 +220,13 @@ protected virtual string LoadContent() {
218220
private void InitializeContent(string content) {
219221
lock (AnalysisLock) {
220222
LoadContent(content);
223+
221224
var startParse = ContentState < State.Parsing && _parsingTask == null;
222225
var startAnalysis = startParse | (ContentState < State.Analyzing && _analysisTcs?.Task == null);
223226

224227
if (startAnalysis) {
225-
_analysisTcs = new TaskCompletionSource<IDocumentAnalysis>();
228+
ExpectNewAnalysis();
226229
}
227-
228230
if (startParse) {
229231
Parse();
230232
}
@@ -250,6 +252,7 @@ private void LoadContent(string content) {
250252
public void Dispose() => Dispose(true);
251253

252254
protected virtual void Dispose(bool disposing) {
255+
_diagnosticsService?.Remove(Uri);
253256
_allProcessingCts.Cancel();
254257
_allProcessingCts.Dispose();
255258
}
@@ -288,7 +291,7 @@ public async Task<PythonAst> GetAstAsync(CancellationToken cancellationToken = d
288291
Task t = null;
289292
while (true) {
290293
lock (AnalysisLock) {
291-
if(t == _parsingTask) {
294+
if (t == _parsingTask) {
292295
break;
293296
}
294297
cancellationToken.ThrowIfCancellationRequested();
@@ -314,10 +317,8 @@ public async Task<PythonAst> GetAstAsync(CancellationToken cancellationToken = d
314317

315318
public void Update(IEnumerable<DocumentChange> changes) {
316319
lock (AnalysisLock) {
317-
ExpectedAnalysisVersion++;
318-
320+
ExpectNewAnalysis();
319321
_linkedAnalysisCts?.Cancel();
320-
_analysisTcs = new TaskCompletionSource<IDocumentAnalysis>();
321322

322323
_parseCts?.Cancel();
323324
_parseCts = new CancellationTokenSource();
@@ -352,18 +353,22 @@ private void Parse() {
352353
}
353354

354355
private void Parse(CancellationToken cancellationToken) {
355-
var sink = new CollectingErrorSink();
356+
CollectingErrorSink sink = null;
356357
int version;
357358
Parser parser;
358359

359360
//Log?.Log(TraceEventType.Verbose, $"Parse begins: {Name}");
360361

361362
lock (AnalysisLock) {
362363
version = _buffer.Version;
363-
parser = Parser.CreateParser(new StringReader(_buffer.Text), Interpreter.LanguageVersion, new ParserOptions {
364-
StubFile = FilePath != null && Path.GetExtension(FilePath).Equals(".pyi", FileSystem.StringComparison),
365-
ErrorSink = sink
366-
});
364+
var options = new ParserOptions {
365+
StubFile = FilePath != null && Path.GetExtension(FilePath).Equals(".pyi", FileSystem.StringComparison)
366+
};
367+
if (ModuleType == ModuleType.User) {
368+
sink = new CollectingErrorSink();
369+
options.ErrorSink = sink;
370+
}
371+
parser = Parser.CreateParser(new StringReader(_buffer.Text), Interpreter.LanguageVersion, options);
367372
}
368373

369374
var ast = parser.ParseFile();
@@ -376,7 +381,13 @@ private void Parse(CancellationToken cancellationToken) {
376381
throw new OperationCanceledException();
377382
}
378383
_ast = ast;
379-
_parseErrors = sink.Diagnostics;
384+
_parseErrors = sink?.Diagnostics ?? Array.Empty<DiagnosticsEntry>();
385+
386+
// Do not report issues with libraries or stubs
387+
if (sink != null) {
388+
_diagnosticsService?.Replace(Uri, _parseErrors);
389+
}
390+
380391
_parsingTask = null;
381392
ContentState = State.Parsed;
382393
}
@@ -428,11 +439,10 @@ public override void Add(string message, SourceSpan span, int errorCode, Severit
428439
public void NotifyAnalysisPending() {
429440
lock (AnalysisLock) {
430441
// The notification comes from the analyzer when it needs to invalidate
431-
// current analysis since one of the dependencies changed. Upon text
432-
// buffer change the version may be incremented twice - once in Update()
433-
// and then here. This is normal.
434-
ExpectedAnalysisVersion++;
435-
_analysisTcs = _analysisTcs ?? new TaskCompletionSource<IDocumentAnalysis>();
442+
// current analysis since one of the dependencies changed. If text
443+
// buffer changed then the notification won't come since the analyzer
444+
// filters out original initiator of the analysis.
445+
ExpectNewAnalysis();
436446
//Log?.Log(TraceEventType.Verbose, $"Analysis pending: {Name}");
437447
}
438448
}
@@ -448,10 +458,11 @@ public virtual bool NotifyAnalysisComplete(IDocumentAnalysis analysis) {
448458
// to perform additional actions on the completed analysis such
449459
// as declare additional variables, etc.
450460
OnAnalysisComplete();
461+
ContentState = State.Analyzed;
451462

452-
_analysisTcs.TrySetResult(analysis);
463+
var tcs = _analysisTcs;
453464
_analysisTcs = null;
454-
ContentState = State.Analyzed;
465+
tcs.TrySetResult(analysis);
455466

456467
NewAnalysis?.Invoke(this, EventArgs.Empty);
457468
return true;
@@ -477,6 +488,11 @@ public Task<IDocumentAnalysis> GetAnalysisAsync(CancellationToken cancellationTo
477488
}
478489
#endregion
479490

491+
private void ExpectNewAnalysis() {
492+
ExpectedAnalysisVersion++;
493+
_analysisTcs = _analysisTcs ?? new TaskCompletionSource<IDocumentAnalysis>();
494+
}
495+
480496
private string TryGetDocFromModuleInitFile() {
481497
if (string.IsNullOrEmpty(FilePath) || !FileSystem.FileExists(FilePath)) {
482498
return string.Empty;

0 commit comments

Comments
 (0)