From b42ac37aac08e64c943c9d9d61ffa1ab18a888c8 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 25 Dec 2017 10:57:54 +0200 Subject: [PATCH 001/178] Moved initial logig to fork. --- .../Services/Diagnostics/CodeCheckService.cs | 10 +- .../Diagnostics/RoslynAnalyzerService.cs | 130 ++++++++++++++++++ 2 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index a857343f2a..eddfe6835c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using System.Composition; +using System.Composition; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -14,11 +13,13 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class CodeCheckService : IRequestHandler { private OmniSharpWorkspace _workspace; + private readonly RoslynAnalyzerService _roslynAnalyzer; [ImportingConstructor] - public CodeCheckService(OmniSharpWorkspace workspace) + public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService roslynAnalyzer) { _workspace = workspace; + _roslynAnalyzer = roslynAnalyzer; } public async Task Handle(CodeCheckRequest request) @@ -28,7 +29,8 @@ public async Task Handle(CodeCheckRequest request) : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); var quickFixes = await documents.FindDiagnosticLocationsAsync(); - return new QuickFixResponse(quickFixes); + _roslynAnalyzer.QueueForAnalysis(documents.Select(x => x.Project).Distinct()); + return new QuickFixResponse(quickFixes.Concat(_roslynAnalyzer.CurrentDiagnosticResults)); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs new file mode 100644 index 0000000000..5f78709a1a --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Extensions.Logging; +using OmniSharp.Models.Diagnostics; + +namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics +{ + [Shared] + [Export(typeof(RoslynAnalyzerService))] + public class RoslynAnalyzerService + { + private readonly ImmutableArray _analyzers; + private readonly ILogger _logger; + private ConcurrentDictionary _projects = new ConcurrentDictionary(); + + [ImportingConstructor] + public RoslynAnalyzerService(OmniSharpWorkspace workspace, ExternalFeaturesHostServicesProvider hostServices, ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + _analyzers = hostServices.Assemblies.SelectMany(assembly => + { + try + { + _logger.LogInformation($"Loading analyzers from assembly: {assembly.Location}"); + + return assembly.GetTypes() + .Where(x => typeof(DiagnosticAnalyzer).IsAssignableFrom(x)) + .Where(x => !x.IsAbstract) + .Select(Activator.CreateInstance) + .Where(x => x != null) + .Cast(); + } + catch (ReflectionTypeLoadException ex) + { + _logger.LogError( + $"Tried to load analyzers from extensions, loader error occurred {ex} : {ex.LoaderExceptions}"); + return Enumerable.Empty(); + } + catch (Exception ex) + { + _logger.LogError( + $"Unexpected error during analyzer loading {ex}"); + return Enumerable.Empty(); + } + } + ).ToImmutableArray(); + + //workspace.WorkspaceChanged += OnWorkspaceChanged; + + Task.Run(() => Worker(CancellationToken.None)); + } + + private async Task Worker(CancellationToken token) + { + while (!token.IsCancellationRequested) + { + var work = _projects; + Console.WriteLine("#1"); + // Yeye here we may mis update because of concurrency but lets leave it at this point. + _projects = new ConcurrentDictionary(); + + var result = await Task.WhenAll(work.Select(async x => await Analyze(x.Value))); + + if (result.SelectMany(x => x).Any()) + CurrentDiagnosticResults = result.SelectMany(x => x).ToImmutableArray(); + + Console.WriteLine("#2"); + + await Task.Delay(1000, token); + } + } + + public ImmutableArray CurrentDiagnosticResults { get; private set; } = ImmutableArray.Empty; + + + private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) + { + if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged) + { + QueueForAnalysis(new[] { changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project }); + } + } + + public void QueueForAnalysis(IEnumerable projects) + { + projects.ToList().ForEach(project => + { + Console.WriteLine($"Queue project {project.Name}"); + _projects.TryAdd(project.Id.ToString(), project); + }); + } + + private async Task> Analyze(Project project) + { + if (_analyzers.Length == 0) + return ImmutableArray.Empty; + + var compiled = await project.GetCompilationAsync(); + var analysis = await compiled.WithAnalyzers(_analyzers).GetAnalysisResultAsync(CancellationToken.None); + analysis.GetAllDiagnostics().ToList().ForEach(x => _logger.LogInformation(x.GetMessage())); + return analysis.GetAllDiagnostics().Select(x => AsDiagnosticLocation(x, project)); + } + + private static DiagnosticLocation AsDiagnosticLocation(Diagnostic diagnostic, Project project) + { + var span = diagnostic.Location.GetMappedLineSpan(); + return new DiagnosticLocation + { + FileName = span.Path, + Line = span.StartLinePosition.Line, + Column = span.StartLinePosition.Character, + EndLine = span.EndLinePosition.Line, + EndColumn = span.EndLinePosition.Character, + Text = $"{diagnostic.GetMessage()} ({diagnostic.Id})", + LogLevel = diagnostic.Severity.ToString(), + Id = diagnostic.Id, + Projects = new List { project.Name } + }; + } + } +} \ No newline at end of file From d2f872fb18f55b88a58603763f1cec416352f61b Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 25 Dec 2017 11:33:48 +0200 Subject: [PATCH 002/178] Version with 'by project' results. --- .../Services/Diagnostics/CodeCheckService.cs | 9 ++- .../Diagnostics/RoslynAnalyzerService.cs | 75 +++++++++---------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index eddfe6835c..7efd4cb8c1 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -29,8 +29,13 @@ public async Task Handle(CodeCheckRequest request) : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); var quickFixes = await documents.FindDiagnosticLocationsAsync(); - _roslynAnalyzer.QueueForAnalysis(documents.Select(x => x.Project).Distinct()); - return new QuickFixResponse(quickFixes.Concat(_roslynAnalyzer.CurrentDiagnosticResults)); + //_roslynAnalyzer.QueueForAnalysis(documents.Select(x => x.Project).Distinct()); + + var analyzerResults = + _roslynAnalyzer.GetCurrentDiagnosticResults().Where(x => + request.FileName == null || x.FileName == request.FileName); + + return new QuickFixResponse(quickFixes.Concat(analyzerResults)); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 5f78709a1a..7ecfc6deec 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -20,41 +20,42 @@ public class RoslynAnalyzerService { private readonly ImmutableArray _analyzers; private readonly ILogger _logger; - private ConcurrentDictionary _projects = new ConcurrentDictionary(); + private ConcurrentDictionary _workQueue = new ConcurrentDictionary(); + private ConcurrentDictionary> _results = new ConcurrentDictionary>(); [ImportingConstructor] public RoslynAnalyzerService(OmniSharpWorkspace workspace, ExternalFeaturesHostServicesProvider hostServices, ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); _analyzers = hostServices.Assemblies.SelectMany(assembly => + { + try + { + _logger.LogInformation($"Loading analyzers from assembly: {assembly.Location}"); + + return assembly.GetTypes() + .Where(x => typeof(DiagnosticAnalyzer).IsAssignableFrom(x)) + .Where(x => !x.IsAbstract) + .Select(Activator.CreateInstance) + .Where(x => x != null) + .Cast(); + } + catch (ReflectionTypeLoadException ex) + { + _logger.LogError( + $"Tried to load analyzers from extensions, loader error occurred {ex} : {ex.LoaderExceptions}"); + return Enumerable.Empty(); + } + catch (Exception ex) { - try - { - _logger.LogInformation($"Loading analyzers from assembly: {assembly.Location}"); - - return assembly.GetTypes() - .Where(x => typeof(DiagnosticAnalyzer).IsAssignableFrom(x)) - .Where(x => !x.IsAbstract) - .Select(Activator.CreateInstance) - .Where(x => x != null) - .Cast(); - } - catch (ReflectionTypeLoadException ex) - { - _logger.LogError( - $"Tried to load analyzers from extensions, loader error occurred {ex} : {ex.LoaderExceptions}"); - return Enumerable.Empty(); - } - catch (Exception ex) - { - _logger.LogError( - $"Unexpected error during analyzer loading {ex}"); - return Enumerable.Empty(); - } + _logger.LogError( + $"Unexpected error during analyzer loading {ex}"); + return Enumerable.Empty(); } + } ).ToImmutableArray(); - //workspace.WorkspaceChanged += OnWorkspaceChanged; + workspace.WorkspaceChanged += OnWorkspaceChanged; Task.Run(() => Worker(CancellationToken.None)); } @@ -63,39 +64,37 @@ private async Task Worker(CancellationToken token) { while (!token.IsCancellationRequested) { - var work = _projects; - Console.WriteLine("#1"); - // Yeye here we may mis update because of concurrency but lets leave it at this point. - _projects = new ConcurrentDictionary(); + var work = _workQueue; - var result = await Task.WhenAll(work.Select(async x => await Analyze(x.Value))); + // Yeye here we may mis update because of concurrency but lets leave it at this point. + _workQueue = new ConcurrentDictionary(); - if (result.SelectMany(x => x).Any()) - CurrentDiagnosticResults = result.SelectMany(x => x).ToImmutableArray(); + var analyzerResults = await Task.WhenAll(work.Select(async x => new { Project = x.Key, Result = await Analyze(x.Value)})); - Console.WriteLine("#2"); + analyzerResults.ToList().ForEach(result => _results[result.Project] = result.Result); await Task.Delay(1000, token); } } - public ImmutableArray CurrentDiagnosticResults { get; private set; } = ImmutableArray.Empty; - + public IEnumerable GetCurrentDiagnosticResults() => _results.SelectMany(x => x.Value); private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged) { + var project = changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project; + _logger.LogInformation($"Queued {project.Name}"); QueueForAnalysis(new[] { changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project }); } } - public void QueueForAnalysis(IEnumerable projects) + private void QueueForAnalysis(IEnumerable projects) { projects.ToList().ForEach(project => { Console.WriteLine($"Queue project {project.Name}"); - _projects.TryAdd(project.Id.ToString(), project); + _workQueue.TryAdd(project.Id.ToString(), project); }); } @@ -127,4 +126,4 @@ private static DiagnosticLocation AsDiagnosticLocation(Diagnostic diagnostic, Pr }; } } -} \ No newline at end of file +} From d366b61b3bcb541f31cd7a6516031e96f83fd682 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 25 Dec 2017 12:06:47 +0200 Subject: [PATCH 003/178] Now analyzers run for all projects after startup. --- .../Services/Diagnostics/CodeCheckService.cs | 1 - .../Diagnostics/RoslynAnalyzerService.cs | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 7efd4cb8c1..f0e8d69f8e 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -29,7 +29,6 @@ public async Task Handle(CodeCheckRequest request) : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); var quickFixes = await documents.FindDiagnosticLocationsAsync(); - //_roslynAnalyzer.QueueForAnalysis(documents.Select(x => x.Project).Distinct()); var analyzerResults = _roslynAnalyzer.GetCurrentDiagnosticResults().Where(x => diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 7ecfc6deec..e56ac59953 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -21,7 +21,8 @@ public class RoslynAnalyzerService private readonly ImmutableArray _analyzers; private readonly ILogger _logger; private ConcurrentDictionary _workQueue = new ConcurrentDictionary(); - private ConcurrentDictionary> _results = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _results = + new ConcurrentDictionary>(); [ImportingConstructor] public RoslynAnalyzerService(OmniSharpWorkspace workspace, ExternalFeaturesHostServicesProvider hostServices, ILoggerFactory loggerFactory) @@ -58,18 +59,22 @@ public RoslynAnalyzerService(OmniSharpWorkspace workspace, ExternalFeaturesHostS workspace.WorkspaceChanged += OnWorkspaceChanged; Task.Run(() => Worker(CancellationToken.None)); + Task.Run(() => + { + QueueForAnalysis(workspace.CurrentSolution.Projects); + }); } private async Task Worker(CancellationToken token) { while (!token.IsCancellationRequested) { - var work = _workQueue; + var currentWork = _workQueue; // Yeye here we may mis update because of concurrency but lets leave it at this point. _workQueue = new ConcurrentDictionary(); - var analyzerResults = await Task.WhenAll(work.Select(async x => new { Project = x.Key, Result = await Analyze(x.Value)})); + var analyzerResults = await Task.WhenAll(currentWork.Select(async x => new { Project = x.Key, Result = await Analyze(x.Value)})); analyzerResults.ToList().ForEach(result => _results[result.Project] = result.Result); @@ -104,8 +109,10 @@ private async Task> Analyze(Project project) return ImmutableArray.Empty; var compiled = await project.GetCompilationAsync(); - var analysis = await compiled.WithAnalyzers(_analyzers).GetAnalysisResultAsync(CancellationToken.None); - analysis.GetAllDiagnostics().ToList().ForEach(x => _logger.LogInformation(x.GetMessage())); + var analysis = await compiled + .WithAnalyzers(_analyzers) + .GetAnalysisResultAsync(CancellationToken.None); + return analysis.GetAllDiagnostics().Select(x => AsDiagnosticLocation(x, project)); } From b374a3f19fb2c6b7f7e4d193c099c47eeda80e13 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 25 Dec 2017 12:09:16 +0200 Subject: [PATCH 004/178] Small refactorings. --- .../Services/Diagnostics/RoslynAnalyzerService.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index e56ac59953..11bdc22cb4 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -35,7 +35,7 @@ public RoslynAnalyzerService(OmniSharpWorkspace workspace, ExternalFeaturesHostS _logger.LogInformation($"Loading analyzers from assembly: {assembly.Location}"); return assembly.GetTypes() - .Where(x => typeof(DiagnosticAnalyzer).IsAssignableFrom(x)) + .Where(typeof(DiagnosticAnalyzer).IsAssignableFrom) .Where(x => !x.IsAbstract) .Select(Activator.CreateInstance) .Where(x => x != null) @@ -59,10 +59,7 @@ public RoslynAnalyzerService(OmniSharpWorkspace workspace, ExternalFeaturesHostS workspace.WorkspaceChanged += OnWorkspaceChanged; Task.Run(() => Worker(CancellationToken.None)); - Task.Run(() => - { - QueueForAnalysis(workspace.CurrentSolution.Projects); - }); + Task.Run(() => QueueForAnalysis(workspace.CurrentSolution.Projects)); } private async Task Worker(CancellationToken token) From 6ce7e66c7d25d334b587d8f136ab4d9d1311da31 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 25 Dec 2017 12:16:03 +0200 Subject: [PATCH 005/178] Small refactoring. --- .../Services/Diagnostics/RoslynAnalyzerService.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 11bdc22cb4..9e4bcc79eb 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -71,9 +71,17 @@ private async Task Worker(CancellationToken token) // Yeye here we may mis update because of concurrency but lets leave it at this point. _workQueue = new ConcurrentDictionary(); - var analyzerResults = await Task.WhenAll(currentWork.Select(async x => new { Project = x.Key, Result = await Analyze(x.Value)})); - - analyzerResults.ToList().ForEach(result => _results[result.Project] = result.Result); + var analyzerResults = await Task + .WhenAll(currentWork + .Select(async x => new + { + Project = x.Key, + Result = await Analyze(x.Value) + })); + + analyzerResults + .ToList() + .ForEach(result => _results[result.Project] = result.Result); await Task.Delay(1000, token); } From cc8f2510b9bf4c9290c84d2aade03b6cab06113f Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 26 Dec 2017 22:43:22 +0200 Subject: [PATCH 006/178] Refactored analyzers to providers. --- .../Diagnostics/RoslynAnalyzerService.cs | 57 +++++++------------ .../Services/AbstractCodeActionProvider.cs | 9 ++- .../Services/ICodeActionProvider.cs | 2 + 3 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 9e4bcc79eb..2985ae713c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Extensions.Logging; using OmniSharp.Models.Diagnostics; +using OmniSharp.Services; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { @@ -18,43 +19,20 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics [Export(typeof(RoslynAnalyzerService))] public class RoslynAnalyzerService { - private readonly ImmutableArray _analyzers; private readonly ILogger _logger; private ConcurrentDictionary _workQueue = new ConcurrentDictionary(); private readonly ConcurrentDictionary> _results = new ConcurrentDictionary>(); + private readonly IEnumerable providers; [ImportingConstructor] - public RoslynAnalyzerService(OmniSharpWorkspace workspace, ExternalFeaturesHostServicesProvider hostServices, ILoggerFactory loggerFactory) + public RoslynAnalyzerService( + OmniSharpWorkspace workspace, + [ImportMany] IEnumerable providers, + ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); - _analyzers = hostServices.Assemblies.SelectMany(assembly => - { - try - { - _logger.LogInformation($"Loading analyzers from assembly: {assembly.Location}"); - - return assembly.GetTypes() - .Where(typeof(DiagnosticAnalyzer).IsAssignableFrom) - .Where(x => !x.IsAbstract) - .Select(Activator.CreateInstance) - .Where(x => x != null) - .Cast(); - } - catch (ReflectionTypeLoadException ex) - { - _logger.LogError( - $"Tried to load analyzers from extensions, loader error occurred {ex} : {ex.LoaderExceptions}"); - return Enumerable.Empty(); - } - catch (Exception ex) - { - _logger.LogError( - $"Unexpected error during analyzer loading {ex}"); - return Enumerable.Empty(); - } - } - ).ToImmutableArray(); + this.providers = providers; workspace.WorkspaceChanged += OnWorkspaceChanged; @@ -66,10 +44,7 @@ private async Task Worker(CancellationToken token) { while (!token.IsCancellationRequested) { - var currentWork = _workQueue; - - // Yeye here we may mis update because of concurrency but lets leave it at this point. - _workQueue = new ConcurrentDictionary(); + var currentWork = GetCurrentWork(); var analyzerResults = await Task .WhenAll(currentWork @@ -87,6 +62,16 @@ private async Task Worker(CancellationToken token) } } + private ConcurrentDictionary GetCurrentWork() + { + lock (_workQueue) + { + var currentWork = _workQueue; + _workQueue = new ConcurrentDictionary(); + return currentWork; + } + } + public IEnumerable GetCurrentDiagnosticResults() => _results.SelectMany(x => x.Value); private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) @@ -94,7 +79,6 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged) { var project = changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project; - _logger.LogInformation($"Queued {project.Name}"); QueueForAnalysis(new[] { changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project }); } } @@ -103,19 +87,18 @@ private void QueueForAnalysis(IEnumerable projects) { projects.ToList().ForEach(project => { - Console.WriteLine($"Queue project {project.Name}"); _workQueue.TryAdd(project.Id.ToString(), project); }); } private async Task> Analyze(Project project) { - if (_analyzers.Length == 0) + if (!this.providers.SelectMany(x => x.CodeDiagnosticAnalyzerProviders).Any()) return ImmutableArray.Empty; var compiled = await project.GetCompilationAsync(); var analysis = await compiled - .WithAnalyzers(_analyzers) + .WithAnalyzers(this.providers.SelectMany(x => x.CodeDiagnosticAnalyzerProviders).ToImmutableArray()) .GetAnalysisResultAsync(CancellationToken.None); return analysis.GetAllDiagnostics().Select(x => AsDiagnosticLocation(x, project)); diff --git a/src/OmniSharp.Roslyn/Services/AbstractCodeActionProvider.cs b/src/OmniSharp.Roslyn/Services/AbstractCodeActionProvider.cs index 90627f1024..33210321e5 100644 --- a/src/OmniSharp.Roslyn/Services/AbstractCodeActionProvider.cs +++ b/src/OmniSharp.Roslyn/Services/AbstractCodeActionProvider.cs @@ -4,6 +4,7 @@ using System.Reflection; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Diagnostics; namespace OmniSharp.Services { @@ -12,7 +13,7 @@ public abstract class AbstractCodeActionProvider : ICodeActionProvider public string ProviderName { get; } public ImmutableArray CodeRefactoringProviders { get; } public ImmutableArray CodeFixProviders { get; } - + public ImmutableArray CodeDiagnosticAnalyzerProviders { get; } public ImmutableArray Assemblies { get; } protected AbstractCodeActionProvider(string providerName, ImmutableArray assemblies) @@ -39,6 +40,12 @@ protected AbstractCodeActionProvider(string providerName, ImmutableArray CreateInstance(type)) .Where(instance => instance != null) .ToImmutableArray(); + + this.CodeDiagnosticAnalyzerProviders = types + .Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) + .Select(type => CreateInstance(type)) + .Where(instance => instance != null) + .ToImmutableArray(); } private T CreateInstance(Type type) where T : class diff --git a/src/OmniSharp.Roslyn/Services/ICodeActionProvider.cs b/src/OmniSharp.Roslyn/Services/ICodeActionProvider.cs index b15f4a1b0c..6cc9d3bc21 100644 --- a/src/OmniSharp.Roslyn/Services/ICodeActionProvider.cs +++ b/src/OmniSharp.Roslyn/Services/ICodeActionProvider.cs @@ -2,6 +2,7 @@ using System.Reflection; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.Diagnostics; namespace OmniSharp.Services { @@ -9,6 +10,7 @@ public interface ICodeActionProvider { ImmutableArray CodeRefactoringProviders { get; } ImmutableArray CodeFixProviders { get; } + ImmutableArray CodeDiagnosticAnalyzerProviders { get; } ImmutableArray Assemblies { get; } } } From 978fbdd3dd5693c6bf295ca728227080bf87426c Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jan 2018 11:50:59 +0200 Subject: [PATCH 007/178] Refactored compilation as single solution bound (review fix). --- global.json | 2 +- .../Services/Diagnostics/RoslynAnalyzerService.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/global.json b/global.json index f5386d4315..918ac01798 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.0.0" + "version": "2.1.0" } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 2985ae713c..ebfd5a3ef8 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -99,9 +99,9 @@ private async Task> Analyze(Project project) var compiled = await project.GetCompilationAsync(); var analysis = await compiled .WithAnalyzers(this.providers.SelectMany(x => x.CodeDiagnosticAnalyzerProviders).ToImmutableArray()) - .GetAnalysisResultAsync(CancellationToken.None); + .GetAllDiagnosticsAsync(); - return analysis.GetAllDiagnostics().Select(x => AsDiagnosticLocation(x, project)); + return analysis.Select(x => AsDiagnosticLocation(x, project)); } private static DiagnosticLocation AsDiagnosticLocation(Diagnostic diagnostic, Project project) From 80801ed68274ce74a05e9df472995e2576ec048d Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Jan 2018 11:55:28 +0200 Subject: [PATCH 008/178] Undoed accidental change. --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 918ac01798..f5386d4315 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "2.1.0" + "version": "2.0.0" } } \ No newline at end of file From 49766605dc7ffcd89d8300eb8b99dc33085cd2fa Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 22 Jul 2018 13:55:05 +0300 Subject: [PATCH 009/178] Before debugging strange type error. --- .../ProjectFile/ProjectFileInfoExtensions.cs | 87 ++++++++++++++++++- .../Diagnostics/RoslynAnalyzerService.cs | 6 +- 2 files changed, 88 insertions(+), 5 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index 7ad5386fa0..988b0cf095 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -1,6 +1,12 @@ -using System.IO; +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Newtonsoft.Json; using OmniSharp.Helpers; namespace OmniSharp.MSBuild.ProjectFile @@ -37,6 +43,9 @@ public static CSharpCompilationOptions CreateCompilationOptions(this ProjectFile public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo) { + var tempHardCodedPath = @"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"; + + // SAVPEK TODO: Add analyzer references here! return ProjectInfo.Create( id: projectFileInfo.Id, version: VersionStamp.Create(), @@ -45,7 +54,81 @@ public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo language: LanguageNames.CSharp, filePath: projectFileInfo.FilePath, outputFilePath: projectFileInfo.TargetPath, - compilationOptions: projectFileInfo.CreateCompilationOptions()); + compilationOptions: projectFileInfo.CreateCompilationOptions(), + analyzerReferences: new[] { new OmnisharpAnalyzerReference(tempHardCodedPath)}); + } + } + + public class OmnisharpAnalyzerReference : AnalyzerReference + { + private readonly string assemblyPath; + private readonly string id; + + [JsonConstructor] + public OmnisharpAnalyzerReference() :base() {} + + public OmnisharpAnalyzerReference(string assemblyPath) + { + this.assemblyPath = assemblyPath; + this.id = Guid.NewGuid().ToString(); + } + + private T CreateInstance(Type type) where T : class + { + try + { + var defaultCtor = type.GetConstructor(new Type[] { }); + + return defaultCtor != null + ? (T)Activator.CreateInstance(type) + : null; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to create instrance of {type.FullName} in {type.AssemblyQualifiedName}.", ex); + } + } + + public override string FullPath => this.assemblyPath; + + public override object Id => this.id; + + public override string Display => this.assemblyPath; + + public override ImmutableArray GetAnalyzers(string language) + { + var assembly = Assembly.LoadFrom(assemblyPath); + + var types = assembly.GetTypes() + .Where(type => !type.GetTypeInfo().IsInterface && + !type.GetTypeInfo().IsAbstract && + !type.GetTypeInfo().ContainsGenericParameters) + .ToList(); + + return types + .Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) + .Select(type => CreateInstance(type)) + .Where(instance => instance != null) + .ToList() + .ToImmutableArray(); + } + + public override ImmutableArray GetAnalyzersForAllLanguages() + { + var assembly = Assembly.LoadFrom(assemblyPath); + + var types = assembly.GetTypes() + .Where(type => !type.GetTypeInfo().IsInterface && + !type.GetTypeInfo().IsAbstract && + !type.GetTypeInfo().ContainsGenericParameters) + .ToList(); + + return types + .Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) + .Select(type => CreateInstance(type)) + .Where(instance => instance != null) + .ToList() + .ToImmutableArray(); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index ebfd5a3ef8..94f2b5451f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -27,8 +27,8 @@ public class RoslynAnalyzerService [ImportingConstructor] public RoslynAnalyzerService( - OmniSharpWorkspace workspace, - [ImportMany] IEnumerable providers, + OmniSharpWorkspace workspace, + [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory) { _logger = loggerFactory.CreateLogger(); @@ -58,7 +58,7 @@ private async Task Worker(CancellationToken token) .ToList() .ForEach(result => _results[result.Project] = result.Result); - await Task.Delay(1000, token); + await Task.Delay(1500, token); } } From 86d197a89c5f721a599604b8a3c71d8382b56c40 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 22 Jul 2018 14:53:26 +0300 Subject: [PATCH 010/178] Simple first working version. --- .../ProjectFile/ProjectFileInfoExtensions.cs | 68 ++++++++++++++++++- .../Diagnostics/RoslynAnalyzerService.cs | 14 +++- 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index 988b0cf095..6ddf3d3ab7 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -43,7 +43,7 @@ public static CSharpCompilationOptions CreateCompilationOptions(this ProjectFile public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo) { - var tempHardCodedPath = @"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"; + //var tempHardCodedPath = @"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"; // SAVPEK TODO: Add analyzer references here! return ProjectInfo.Create( @@ -55,7 +55,71 @@ public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo filePath: projectFileInfo.FilePath, outputFilePath: projectFileInfo.TargetPath, compilationOptions: projectFileInfo.CreateCompilationOptions(), - analyzerReferences: new[] { new OmnisharpAnalyzerReference(tempHardCodedPath)}); + analyzerReferences: new AnalyzerReference[] { new AnalyzerFileReference(@"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll", new AnalyzerAssemblyLoader()) }); + } + } + + public class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader + { + public void AddDependencyLocation(string fullPath) + { + } + + public Assembly LoadFromPath(string fullPath) + { + return Assembly.LoadFrom(@"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"); + } + } + + public class OmnisharpAnalyzerReference2 : AnalyzerReference, IEquatable + { + public override string FullPath => "foobar"; + public override string Display => "foo"; + public override object Id => "foobarlol"; + + public bool Equals(AnalyzerReference other) + { + return other.Id == this.Id; + } + + public override ImmutableArray GetAnalyzers(string language) + { + return new DiagnosticAnalyzer[] { }.ToImmutableArray(); +// var assembly = Assembly.LoadFrom(@"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"); +// +// var types = assembly.GetTypes() +// .Where(type => !type.GetTypeInfo().IsInterface && +// !type.GetTypeInfo().IsAbstract && +// !type.GetTypeInfo().ContainsGenericParameters) +// .ToList(); +// +// return types +// .Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) +// .Select(type => CreateInstance(type)) +// .Where(instance => instance != null) +// .ToList() +// .ToImmutableArray(); + } + +// private T CreateInstance(Type type) where T : class +// { +// try +// { +// var defaultCtor = type.GetConstructor(new Type[] { }); +// +// return defaultCtor != null +// ? (T)Activator.CreateInstance(type) +// : null; +// } +// catch (Exception ex) +// { +// throw new InvalidOperationException($"Failed to create instrance of {type.FullName} in {type.AssemblyQualifiedName}.", ex); +// } +// } + + public override ImmutableArray GetAnalyzersForAllLanguages() + { + return new DiagnosticAnalyzer[] { }.ToImmutableArray(); } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 94f2b5451f..7d3e6d844d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -93,14 +93,22 @@ private void QueueForAnalysis(IEnumerable projects) private async Task> Analyze(Project project) { - if (!this.providers.SelectMany(x => x.CodeDiagnosticAnalyzerProviders).Any()) - return ImmutableArray.Empty; + //if (!this.providers.SelectMany(x => x.CodeDiagnosticAnalyzerProviders).Any()) + // return ImmutableArray.Empty; var compiled = await project.GetCompilationAsync(); + + var allAnalyzers = this.providers + .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) + .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())) + .ToImmutableArray(); + var analysis = await compiled - .WithAnalyzers(this.providers.SelectMany(x => x.CodeDiagnosticAnalyzerProviders).ToImmutableArray()) + .WithAnalyzers(allAnalyzers) .GetAllDiagnosticsAsync(); + var foo = analysis.ToList(); + return analysis.Select(x => AsDiagnosticLocation(x, project)); } From 544aec8dd2a359009006536068d82b8f4c11bffd Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 22 Jul 2018 14:56:27 +0300 Subject: [PATCH 011/178] Cleanup. --- .../ProjectFile/ProjectFileInfoExtensions.cs | 127 ------------------ 1 file changed, 127 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index 6ddf3d3ab7..096037b413 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -43,8 +43,6 @@ public static CSharpCompilationOptions CreateCompilationOptions(this ProjectFile public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo) { - //var tempHardCodedPath = @"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"; - // SAVPEK TODO: Add analyzer references here! return ProjectInfo.Create( id: projectFileInfo.Id, @@ -70,129 +68,4 @@ public Assembly LoadFromPath(string fullPath) return Assembly.LoadFrom(@"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"); } } - - public class OmnisharpAnalyzerReference2 : AnalyzerReference, IEquatable - { - public override string FullPath => "foobar"; - public override string Display => "foo"; - public override object Id => "foobarlol"; - - public bool Equals(AnalyzerReference other) - { - return other.Id == this.Id; - } - - public override ImmutableArray GetAnalyzers(string language) - { - return new DiagnosticAnalyzer[] { }.ToImmutableArray(); -// var assembly = Assembly.LoadFrom(@"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"); -// -// var types = assembly.GetTypes() -// .Where(type => !type.GetTypeInfo().IsInterface && -// !type.GetTypeInfo().IsAbstract && -// !type.GetTypeInfo().ContainsGenericParameters) -// .ToList(); -// -// return types -// .Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) -// .Select(type => CreateInstance(type)) -// .Where(instance => instance != null) -// .ToList() -// .ToImmutableArray(); - } - -// private T CreateInstance(Type type) where T : class -// { -// try -// { -// var defaultCtor = type.GetConstructor(new Type[] { }); -// -// return defaultCtor != null -// ? (T)Activator.CreateInstance(type) -// : null; -// } -// catch (Exception ex) -// { -// throw new InvalidOperationException($"Failed to create instrance of {type.FullName} in {type.AssemblyQualifiedName}.", ex); -// } -// } - - public override ImmutableArray GetAnalyzersForAllLanguages() - { - return new DiagnosticAnalyzer[] { }.ToImmutableArray(); - } - } - - public class OmnisharpAnalyzerReference : AnalyzerReference - { - private readonly string assemblyPath; - private readonly string id; - - [JsonConstructor] - public OmnisharpAnalyzerReference() :base() {} - - public OmnisharpAnalyzerReference(string assemblyPath) - { - this.assemblyPath = assemblyPath; - this.id = Guid.NewGuid().ToString(); - } - - private T CreateInstance(Type type) where T : class - { - try - { - var defaultCtor = type.GetConstructor(new Type[] { }); - - return defaultCtor != null - ? (T)Activator.CreateInstance(type) - : null; - } - catch (Exception ex) - { - throw new InvalidOperationException($"Failed to create instrance of {type.FullName} in {type.AssemblyQualifiedName}.", ex); - } - } - - public override string FullPath => this.assemblyPath; - - public override object Id => this.id; - - public override string Display => this.assemblyPath; - - public override ImmutableArray GetAnalyzers(string language) - { - var assembly = Assembly.LoadFrom(assemblyPath); - - var types = assembly.GetTypes() - .Where(type => !type.GetTypeInfo().IsInterface && - !type.GetTypeInfo().IsAbstract && - !type.GetTypeInfo().ContainsGenericParameters) - .ToList(); - - return types - .Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) - .Select(type => CreateInstance(type)) - .Where(instance => instance != null) - .ToList() - .ToImmutableArray(); - } - - public override ImmutableArray GetAnalyzersForAllLanguages() - { - var assembly = Assembly.LoadFrom(assemblyPath); - - var types = assembly.GetTypes() - .Where(type => !type.GetTypeInfo().IsInterface && - !type.GetTypeInfo().IsAbstract && - !type.GetTypeInfo().ContainsGenericParameters) - .ToList(); - - return types - .Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) - .Select(type => CreateInstance(type)) - .Where(instance => instance != null) - .ToList() - .ToImmutableArray(); - } - } } From dc527fb7ba004f7632f804d32987862c19c07450 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 22 Jul 2018 15:38:47 +0300 Subject: [PATCH 012/178] Implemented loading from project analyzer references. --- .../ProjectFile/AnalyzerAssemblyLoader.cs | 26 +++++++++++++++++ .../ProjectFile/ProjectFileInfoExtensions.cs | 29 ++++++++++--------- 2 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 src/OmniSharp.MSBuild/ProjectFile/AnalyzerAssemblyLoader.cs diff --git a/src/OmniSharp.MSBuild/ProjectFile/AnalyzerAssemblyLoader.cs b/src/OmniSharp.MSBuild/ProjectFile/AnalyzerAssemblyLoader.cs new file mode 100644 index 0000000000..3ed856187e --- /dev/null +++ b/src/OmniSharp.MSBuild/ProjectFile/AnalyzerAssemblyLoader.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Concurrent; +using System.Reflection; +using Microsoft.CodeAnalysis; + +namespace OmniSharp.MSBuild.ProjectFile +{ + public class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader + { + private readonly ConcurrentDictionary _assemblyPaths = new ConcurrentDictionary(); + + public void AddDependencyLocation(string fullPath) + { + if(!_assemblyPaths.ContainsKey(fullPath)) + _assemblyPaths.TryAdd(fullPath, Assembly.LoadFrom(fullPath)); + } + + public Assembly LoadFromPath(string fullPath) + { + if (!_assemblyPaths.ContainsKey(fullPath)) + throw new InvalidOperationException($"Could not find analyzer reference '{fullPath}' from cache."); + + return _assemblyPaths[fullPath]; + } + } +} diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index 096037b413..01633167ca 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Immutable; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; @@ -43,7 +41,8 @@ public static CSharpCompilationOptions CreateCompilationOptions(this ProjectFile public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo) { - // SAVPEK TODO: Add analyzer references here! + var analyzerReferences = ResolveAnalyzerReferencesForProject(projectFileInfo); + return ProjectInfo.Create( id: projectFileInfo.Id, version: VersionStamp.Create(), @@ -53,19 +52,23 @@ public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo filePath: projectFileInfo.FilePath, outputFilePath: projectFileInfo.TargetPath, compilationOptions: projectFileInfo.CreateCompilationOptions(), - analyzerReferences: new AnalyzerReference[] { new AnalyzerFileReference(@"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll", new AnalyzerAssemblyLoader()) }); + analyzerReferences: analyzerReferences); } - } - public class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader - { - public void AddDependencyLocation(string fullPath) + private static IEnumerable ResolveAnalyzerReferencesForProject(ProjectFileInfo projectFileInfo) { - } + return projectFileInfo.Analyzers + .GroupBy(x => Path.GetDirectoryName(x)) + .Select(singleAnalyzerPackageGroup => + { + // Is there better way to figure out entry assembly for specific nuget analyzer package? + var analyzerMainAssembly = singleAnalyzerPackageGroup.Single(x => x.EndsWith("Analyzers.dll") || x.EndsWith("Analyzer.dll")); - public Assembly LoadFromPath(string fullPath) - { - return Assembly.LoadFrom(@"C:\RoslynAnalyzers\Roslynator.CSharp.Analyzers.dll"); + var assemblyLoader = new AnalyzerAssemblyLoader(); + singleAnalyzerPackageGroup.ToList().ForEach(x => assemblyLoader.AddDependencyLocation(x)); + + return new AnalyzerFileReference(analyzerMainAssembly, assemblyLoader); + }); } } } From 68fef87d018290af75c120a5c1b681d3fbb0ff91 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 22 Jul 2018 15:39:59 +0300 Subject: [PATCH 013/178] Cleanup. --- .../Services/Diagnostics/RoslynAnalyzerService.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 7d3e6d844d..ad939fb302 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -93,22 +93,20 @@ private void QueueForAnalysis(IEnumerable projects) private async Task> Analyze(Project project) { - //if (!this.providers.SelectMany(x => x.CodeDiagnosticAnalyzerProviders).Any()) - // return ImmutableArray.Empty; - - var compiled = await project.GetCompilationAsync(); - var allAnalyzers = this.providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())) .ToImmutableArray(); + if (!allAnalyzers.Any()) + return ImmutableArray.Empty; + + var compiled = await project.GetCompilationAsync(); + var analysis = await compiled .WithAnalyzers(allAnalyzers) .GetAllDiagnosticsAsync(); - var foo = analysis.ToList(); - return analysis.Select(x => AsDiagnosticLocation(x, project)); } From e11d11bd261ab1f0105a7d7df58ef694515f3148 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 22 Jul 2018 15:41:00 +0300 Subject: [PATCH 014/178] Small lazy invocation tweak. --- .../Services/Diagnostics/RoslynAnalyzerService.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index ad939fb302..4cf85bd6a2 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -95,8 +95,7 @@ private async Task> Analyze(Project project) { var allAnalyzers = this.providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) - .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())) - .ToImmutableArray(); + .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); if (!allAnalyzers.Any()) return ImmutableArray.Empty; @@ -104,7 +103,7 @@ private async Task> Analyze(Project project) var compiled = await project.GetCompilationAsync(); var analysis = await compiled - .WithAnalyzers(allAnalyzers) + .WithAnalyzers(allAnalyzers.ToImmutableArray()) .GetAllDiagnosticsAsync(); return analysis.Select(x => AsDiagnosticLocation(x, project)); From 9070f09e5f51ffa6fc0927ea912745e80f68c1ce Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 24 Jul 2018 18:36:07 +0300 Subject: [PATCH 015/178] Updated cacellation token handling and way how invalid analyzer ref is handled. --- .../ProjectFile/ProjectFileInfoExtensions.cs | 9 ++++++--- .../Services/Diagnostics/RoslynAnalyzerService.cs | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index 01633167ca..1a508125e9 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -55,14 +55,17 @@ public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo analyzerReferences: analyzerReferences); } - private static IEnumerable ResolveAnalyzerReferencesForProject(ProjectFileInfo projectFileInfo) + private static IEnumerable ResolveAnalyzerReferencesForProject(ProjectFileInfo projectFileInfo) { return projectFileInfo.Analyzers .GroupBy(x => Path.GetDirectoryName(x)) .Select(singleAnalyzerPackageGroup => { - // Is there better way to figure out entry assembly for specific nuget analyzer package? - var analyzerMainAssembly = singleAnalyzerPackageGroup.Single(x => x.EndsWith("Analyzers.dll") || x.EndsWith("Analyzer.dll")); + // Is there better way to figure out entry assembly for specific nuget analyzer package? And is there even entry assembly or is better way to just lookup all referenced assemblies? + var analyzerMainAssembly = singleAnalyzerPackageGroup.SingleOrDefault(x => x.EndsWith("Analyzers.dll") || x.EndsWith("Analyzer.dll")); + + if (analyzerMainAssembly == null) + return (AnalyzerReference)new UnresolvedAnalyzerReference(singleAnalyzerPackageGroup.First()); var assemblyLoader = new AnalyzerAssemblyLoader(); singleAnalyzerPackageGroup.ToList().ForEach(x => assemblyLoader.AddDependencyLocation(x)); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 4cf85bd6a2..578f1a6bae 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -51,7 +51,7 @@ private async Task Worker(CancellationToken token) .Select(async x => new { Project = x.Key, - Result = await Analyze(x.Value) + Result = await Analyze(x.Value, token) })); analyzerResults @@ -91,7 +91,7 @@ private void QueueForAnalysis(IEnumerable projects) }); } - private async Task> Analyze(Project project) + private async Task> Analyze(Project project, CancellationToken token) { var allAnalyzers = this.providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) @@ -100,11 +100,11 @@ private async Task> Analyze(Project project) if (!allAnalyzers.Any()) return ImmutableArray.Empty; - var compiled = await project.GetCompilationAsync(); + var compiled = await project.GetCompilationAsync(token); var analysis = await compiled .WithAnalyzers(allAnalyzers.ToImmutableArray()) - .GetAllDiagnosticsAsync(); + .GetAllDiagnosticsAsync(token); return analysis.Select(x => AsDiagnosticLocation(x, project)); } From 5adf1a17804d11e40dc0fdc354bc43f10f26cafb Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 24 Jul 2018 21:44:55 +0300 Subject: [PATCH 016/178] Tweaked assembly discovery algorithm for analyzers. --- .../ProjectFile/ProjectFileInfoExtensions.cs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index 1a508125e9..77d1a19da7 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -59,18 +59,16 @@ private static IEnumerable ResolveAnalyzerReferencesForProjec { return projectFileInfo.Analyzers .GroupBy(x => Path.GetDirectoryName(x)) - .Select(singleAnalyzerPackageGroup => + .SelectMany(singleAnalyzerPackageGroup => { // Is there better way to figure out entry assembly for specific nuget analyzer package? And is there even entry assembly or is better way to just lookup all referenced assemblies? - var analyzerMainAssembly = singleAnalyzerPackageGroup.SingleOrDefault(x => x.EndsWith("Analyzers.dll") || x.EndsWith("Analyzer.dll")); - - if (analyzerMainAssembly == null) - return (AnalyzerReference)new UnresolvedAnalyzerReference(singleAnalyzerPackageGroup.First()); - - var assemblyLoader = new AnalyzerAssemblyLoader(); - singleAnalyzerPackageGroup.ToList().ForEach(x => assemblyLoader.AddDependencyLocation(x)); - - return new AnalyzerFileReference(analyzerMainAssembly, assemblyLoader); + return singleAnalyzerPackageGroup + .Where(x => x.EndsWith("Analyzers.dll") || x.EndsWith("Analyzer.dll")) + .Select(analyzerMainAssembly => { + var assemblyLoader = new AnalyzerAssemblyLoader(); + singleAnalyzerPackageGroup.ToList().ForEach(x => assemblyLoader.AddDependencyLocation(x)); + return new AnalyzerFileReference(analyzerMainAssembly, assemblyLoader); + }); }); } } From 1d79739c3a4362eebe680fdf56c8d033bf0be2cc Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 25 Jul 2018 16:10:38 +0300 Subject: [PATCH 017/178] Initial tests for analyzers. --- .../ProjectFile/ProjectFileInfoExtensions.cs | 1 - .../Diagnostics/RoslynAnalyzerService.cs | 2 +- .../RoslynAnalyzerFacts.cs | 145 ++++++++++++++++++ tests/TestUtility/TestHelpers.cs | 7 +- 4 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index 77d1a19da7..dc8104449d 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -4,7 +4,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; -using Newtonsoft.Json; using OmniSharp.Helpers; namespace OmniSharp.MSBuild.ProjectFile diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 578f1a6bae..c8f95e1bd7 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -58,7 +58,7 @@ private async Task Worker(CancellationToken token) .ToList() .ForEach(result => _results[result.Project] = result.Result); - await Task.Delay(1500, token); + await Task.Delay(500, token); } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs new file mode 100644 index 0000000000..9f1a90cdc6 --- /dev/null +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -0,0 +1,145 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using OmniSharp.Roslyn.CSharp.Services.Diagnostics; +using TestUtility; +using Xunit; +using Xunit.Abstractions; + +namespace OmniSharp.Roslyn.CSharp.Tests +{ + public class RoslynAnalyzerFacts : AbstractTestFixture + { + public class TestAnalyzerReference : AnalyzerReference + { + public override string FullPath => null; + public override object Id => Display; + public override string Display => nameof(TestAnalyzerReference); + + public override ImmutableArray GetAnalyzers(string language) + { + return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer() }.ToImmutableArray(); + } + + public override ImmutableArray GetAnalyzersForAllLanguages() + { + return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer() }.ToImmutableArray(); + } + } + + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class TestDiagnosticAnalyzer : DiagnosticAnalyzer + { + private static DiagnosticDescriptor Rule = new DiagnosticDescriptor( + nameof(TestDiagnosticAnalyzer), + "Testtitle", + "Type name '{0}' contains lowercase letters", + "Naming", + DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + + public override ImmutableArray SupportedDiagnostics + { + get { return ImmutableArray.Create(Rule); } + } + + public override void Initialize(AnalysisContext context) + { + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context) + { + var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; + if (namedTypeSymbol.Name == "_this_is_invalid_test_class_name") + { + context.ReportDiagnostic(Diagnostic.Create( + Rule, + namedTypeSymbol.Locations[0], + namedTypeSymbol.Name + )); + } + } + } + + public RoslynAnalyzerFacts(ITestOutputHelper output, SharedOmniSharpHostFixture sharedOmniSharpHostFixture) + : base(output, sharedOmniSharpHostFixture) + { + } + + [Fact] + public async Task When_custom_analyzers_are_executed_then_return_results() + { + var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + + SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); + + var testAnalyzerRef = new TestAnalyzerReference(); + + TestHelpers.AddProjectToWorkspace( + SharedOmniSharpTestHost.Workspace, + "project.csproj", + new[] { "netcoreapp2.1" }, + new[] { testFile }, + analyzerRefs: new AnalyzerReference []{ testAnalyzerRef }.ToImmutableArray()); + + var analyzerService = SharedOmniSharpTestHost.GetExport(); + + // TODO: This is hack, requires real wait for result routine. + await Task.Delay(5000); + + Assert.Single( + analyzerService.GetCurrentDiagnosticResults().Where(x => x.Id == nameof(TestDiagnosticAnalyzer))); + } + + [Fact] + public async Task When_custom_analyzer_doesnt_have_match_then_dont_return_it() + { + var testFile = new TestFile("testFile.cs", "class SomeClass { int n = true; }"); + + SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); + + var testAnalyzerRef = new TestAnalyzerReference(); + + TestHelpers.AddProjectToWorkspace( + SharedOmniSharpTestHost.Workspace, + "project.csproj", + new[] { "netcoreapp2.1" }, + new[] { testFile }, + analyzerRefs: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray()); + + var analyzerService = SharedOmniSharpTestHost.GetExport(); + + // TODO: This is hack, requires real wait for result routine. + await Task.Delay(5000); + + Assert.Empty( + analyzerService.GetCurrentDiagnosticResults().Where(x => x.Id == nameof(TestDiagnosticAnalyzer))); + } + + [Fact] + public async Task Always_return_results_from_net_default_analyzers() + { + var testFile = new TestFile("testFile.cs", "class SomeClass { int n = true; }"); + + SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); + + TestHelpers.AddProjectToWorkspace( + SharedOmniSharpTestHost.Workspace, + "project.csproj", + new[] { "netcoreapp2.1" }, + new[] { testFile }); + + var analyzerService = SharedOmniSharpTestHost.GetExport(); + + // TODO: This is hack, requires real wait for result routine. + await Task.Delay(5000); + + Assert.Empty( + analyzerService.GetCurrentDiagnosticResults().Where(x => x.Id == "CS5001")); + } + } +} diff --git a/tests/TestUtility/TestHelpers.cs b/tests/TestUtility/TestHelpers.cs index 7af0b6cfce..2478fb0e47 100644 --- a/tests/TestUtility/TestHelpers.cs +++ b/tests/TestUtility/TestHelpers.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Reflection; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Scripting.Hosting; using Microsoft.Extensions.Logging; using OmniSharp; @@ -36,7 +38,7 @@ public static void AddCsxProjectToWorkspace(OmniSharpWorkspace workspace, TestFi workspace.AddDocument(documentInfo); } - public static void AddProjectToWorkspace(OmniSharpWorkspace workspace, string filePath, string[] frameworks, TestFile[] testFiles) + public static void AddProjectToWorkspace(OmniSharpWorkspace workspace, string filePath, string[] frameworks, TestFile[] testFiles, ImmutableArray analyzerRefs = default) { var versionStamp = VersionStamp.Create(); var references = GetReferences(); @@ -51,7 +53,8 @@ public static void AddProjectToWorkspace(OmniSharpWorkspace workspace, string fi assemblyName: "AssemblyName", language: LanguageNames.CSharp, filePath: filePath, - metadataReferences: references); + metadataReferences: references, + analyzerReferences: analyzerRefs); workspace.AddProject(projectInfo); From fa2721e2a28a99fbc6b6a7e190650fc857e108da Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 25 Jul 2018 16:13:42 +0300 Subject: [PATCH 018/178] Testfix. --- tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 9f1a90cdc6..3fc2bcbe8a 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -138,7 +138,7 @@ public async Task Always_return_results_from_net_default_analyzers() // TODO: This is hack, requires real wait for result routine. await Task.Delay(5000); - Assert.Empty( + Assert.Single( analyzerService.GetCurrentDiagnosticResults().Where(x => x.Id == "CS5001")); } } From 21c670c168451275891cbbe40f9fad7e32f536e8 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 25 Jul 2018 17:34:47 +0300 Subject: [PATCH 019/178] Improved tests. --- .../RoslynAnalyzerFacts.cs | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 3fc2bcbe8a..4b51afe655 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -1,8 +1,10 @@ -using System.Collections.Immutable; +using System; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using OmniSharp.Models.CodeCheck; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using TestUtility; using Xunit; @@ -10,30 +12,44 @@ namespace OmniSharp.Roslyn.CSharp.Tests { - public class RoslynAnalyzerFacts : AbstractTestFixture + public class RoslynAnalyzerFacts : AbstractSingleRequestHandlerTestFixture { + protected override string EndpointName => OmniSharpEndpoints.CodeCheck; + public class TestAnalyzerReference : AnalyzerReference { + private readonly Guid _id; + + public TestAnalyzerReference(Guid testAnalyzerId) + { + _id = testAnalyzerId; + } + public override string FullPath => null; - public override object Id => Display; - public override string Display => nameof(TestAnalyzerReference); + public override object Id => _id.ToString(); + public override string Display => $"{nameof(TestAnalyzerReference)}_{Id}"; public override ImmutableArray GetAnalyzers(string language) { - return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer() }.ToImmutableArray(); + return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer(Id.ToString()) }.ToImmutableArray(); } public override ImmutableArray GetAnalyzersForAllLanguages() { - return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer() }.ToImmutableArray(); + return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer(Id.ToString()) }.ToImmutableArray(); } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TestDiagnosticAnalyzer : DiagnosticAnalyzer { - private static DiagnosticDescriptor Rule = new DiagnosticDescriptor( - nameof(TestDiagnosticAnalyzer), + public TestDiagnosticAnalyzer(string id) + { + this.id = id; + } + + private DiagnosticDescriptor Rule => new DiagnosticDescriptor( + this.id, "Testtitle", "Type name '{0}' contains lowercase letters", "Naming", @@ -41,6 +57,8 @@ public class TestDiagnosticAnalyzer : DiagnosticAnalyzer isEnabledByDefault: true ); + private readonly string id; + public override ImmutableArray SupportedDiagnostics { get { return ImmutableArray.Create(Rule); } @@ -51,7 +69,7 @@ public override void Initialize(AnalysisContext context) context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); } - private static void AnalyzeSymbol(SymbolAnalysisContext context) + private void AnalyzeSymbol(SymbolAnalysisContext context) { var namedTypeSymbol = (INamedTypeSymbol)context.Symbol; if (namedTypeSymbol.Name == "_this_is_invalid_test_class_name") @@ -77,7 +95,9 @@ public async Task When_custom_analyzers_are_executed_then_return_results() SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - var testAnalyzerRef = new TestAnalyzerReference(); + var analyzerId = Guid.NewGuid(); + + var testAnalyzerRef = new TestAnalyzerReference(analyzerId); TestHelpers.AddProjectToWorkspace( SharedOmniSharpTestHost.Workspace, @@ -86,60 +106,44 @@ public async Task When_custom_analyzers_are_executed_then_return_results() new[] { testFile }, analyzerRefs: new AnalyzerReference []{ testAnalyzerRef }.ToImmutableArray()); - var analyzerService = SharedOmniSharpTestHost.GetExport(); + var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - // TODO: This is hack, requires real wait for result routine. - await Task.Delay(5000); - - Assert.Single( - analyzerService.GetCurrentDiagnosticResults().Where(x => x.Id == nameof(TestDiagnosticAnalyzer))); + await AssertForEventuallyMatch( + codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains(analyzerId.ToString()))); } [Fact] - public async Task When_custom_analyzer_doesnt_have_match_then_dont_return_it() + public async Task Always_return_results_from_net_default_analyzers() { var testFile = new TestFile("testFile.cs", "class SomeClass { int n = true; }"); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - var testAnalyzerRef = new TestAnalyzerReference(); - TestHelpers.AddProjectToWorkspace( SharedOmniSharpTestHost.Workspace, "project.csproj", new[] { "netcoreapp2.1" }, - new[] { testFile }, - analyzerRefs: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray()); - - var analyzerService = SharedOmniSharpTestHost.GetExport(); + new[] { testFile }); - // TODO: This is hack, requires real wait for result routine. - await Task.Delay(5000); + var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - Assert.Empty( - analyzerService.GetCurrentDiagnosticResults().Where(x => x.Id == nameof(TestDiagnosticAnalyzer))); + await AssertForEventuallyMatch( + codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains("CS5001"))); } - [Fact] - public async Task Always_return_results_from_net_default_analyzers() + private static async Task AssertForEventuallyMatch(Task func, Predicate check, int retryCount = 50) { - var testFile = new TestFile("testFile.cs", "class SomeClass { int n = true; }"); - - SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - - TestHelpers.AddProjectToWorkspace( - SharedOmniSharpTestHost.Workspace, - "project.csproj", - new[] { "netcoreapp2.1" }, - new[] { testFile }); + while (retryCount-- > 0) + { + var result = await func; - var analyzerService = SharedOmniSharpTestHost.GetExport(); + if (check(result)) + return result; - // TODO: This is hack, requires real wait for result routine. - await Task.Delay(5000); + await Task.Delay(200); + } - Assert.Single( - analyzerService.GetCurrentDiagnosticResults().Where(x => x.Id == "CS5001")); + throw new InvalidOperationException("Timeout expired before meaningfull result returned."); } } } From 7ec5cf0160461099025aafd5b6d90b87159b796b Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 25 Jul 2018 19:36:53 +0300 Subject: [PATCH 020/178] Throtling to avoid certain issues with error reporting. --- .../Diagnostics/RoslynAnalyzerService.cs | 21 ++++++++++++------- .../RoslynAnalyzerFacts.cs | 15 +++++++------ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index c8f95e1bd7..6053802d7b 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Composition; using System.Linq; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -20,11 +19,13 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class RoslynAnalyzerService { private readonly ILogger _logger; - private ConcurrentDictionary _workQueue = new ConcurrentDictionary(); + private ConcurrentDictionary _workQueue = new ConcurrentDictionary(); private readonly ConcurrentDictionary> _results = new ConcurrentDictionary>(); private readonly IEnumerable providers; + private int throttlingMs = 1000; + [ImportingConstructor] public RoslynAnalyzerService( OmniSharpWorkspace workspace, @@ -44,7 +45,7 @@ private async Task Worker(CancellationToken token) { while (!token.IsCancellationRequested) { - var currentWork = GetCurrentWork(); + var currentWork = GetThrottledWork(); var analyzerResults = await Task .WhenAll(currentWork @@ -62,13 +63,17 @@ private async Task Worker(CancellationToken token) } } - private ConcurrentDictionary GetCurrentWork() + private IDictionary GetThrottledWork() { lock (_workQueue) { - var currentWork = _workQueue; - _workQueue = new ConcurrentDictionary(); - return currentWork; + var currentWork = _workQueue + .Where(x => x.Value.modified.AddMilliseconds(this.throttlingMs) < DateTime.UtcNow) + .ToList(); + + currentWork.Select(x => x.Key).ToList().ForEach(key => _workQueue.TryRemove(key, out _)); + + return currentWork.ToDictionary(x => x.Key, x => x.Value.project); } } @@ -87,7 +92,7 @@ private void QueueForAnalysis(IEnumerable projects) { projects.ToList().ForEach(project => { - _workQueue.TryAdd(project.Id.ToString(), project); + _workQueue.AddOrUpdate(project.Id.ToString(), (modified: DateTime.UtcNow, project: project), (key, old) => (modified: DateTime.UtcNow, project: project)); }); } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 4b51afe655..164c330725 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -18,15 +18,15 @@ public class RoslynAnalyzerFacts : AbstractSingleRequestHandlerTestFixture null; - public override object Id => _id.ToString(); + public override object Id => _id; public override string Display => $"{nameof(TestAnalyzerReference)}_{Id}"; public override ImmutableArray GetAnalyzers(string language) @@ -95,7 +95,7 @@ public async Task When_custom_analyzers_are_executed_then_return_results() SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - var analyzerId = Guid.NewGuid(); + var analyzerId = "TS1000".ToString(); var testAnalyzerRef = new TestAnalyzerReference(analyzerId); @@ -107,9 +107,8 @@ public async Task When_custom_analyzers_are_executed_then_return_results() analyzerRefs: new AnalyzerReference []{ testAnalyzerRef }.ToImmutableArray()); var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - await AssertForEventuallyMatch( - codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains(analyzerId.ToString()))); + codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains("CS5001"))); } [Fact] @@ -131,7 +130,7 @@ await AssertForEventuallyMatch( codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains("CS5001"))); } - private static async Task AssertForEventuallyMatch(Task func, Predicate check, int retryCount = 50) + private static async Task AssertForEventuallyMatch(Task func, Predicate check, int retryCount = 10) { while (retryCount-- > 0) { @@ -140,7 +139,7 @@ private static async Task AssertForEventuallyMatch(Task func, Predicate if (check(result)) return result; - await Task.Delay(200); + await Task.Delay(1000); } throw new InvalidOperationException("Timeout expired before meaningfull result returned."); From a8e388ab6c8a15d75f303a437e546fb98262afe6 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 25 Jul 2018 20:19:00 +0300 Subject: [PATCH 021/178] Testfix. --- tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 164c330725..0b98dbb436 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -108,7 +108,7 @@ public async Task When_custom_analyzers_are_executed_then_return_results() var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); await AssertForEventuallyMatch( - codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains("CS5001"))); + codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains(analyzerId))); } [Fact] From 177c41b6ebae2b87d004526e31c3499868c8a3e5 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 26 Jul 2018 09:33:06 +0300 Subject: [PATCH 022/178] Attempt to fix hanging cake tests on travis --- tests/OmniSharp.Cake.Tests/AssemblyInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OmniSharp.Cake.Tests/AssemblyInfo.cs b/tests/OmniSharp.Cake.Tests/AssemblyInfo.cs index 9933b8fd08..9468891954 100644 --- a/tests/OmniSharp.Cake.Tests/AssemblyInfo.cs +++ b/tests/OmniSharp.Cake.Tests/AssemblyInfo.cs @@ -1 +1 @@ -[assembly: Xunit.CollectionBehavior(DisableTestParallelization = true)] +[assembly: Xunit.CollectionBehavior(Xunit.CollectionBehavior.CollectionPerAssembly, DisableTestParallelization = true, MaxParallelThreads = -1)] From 2d0c255ab1cf4ce2446acddf06bae92a7edc95a2 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 28 Jul 2018 19:16:32 +0300 Subject: [PATCH 023/178] Refactored diagnostic service. --- .../Refactoring/V2/AvailableCodeAction.cs | 7 +- .../Refactoring/V2/BaseCodeActionService.cs | 135 +++++------------- 2 files changed, 33 insertions(+), 109 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/AvailableCodeAction.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/AvailableCodeAction.cs index c29e975cf8..26a3d2c6ba 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/AvailableCodeAction.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/AvailableCodeAction.cs @@ -13,12 +13,7 @@ public class AvailableCodeAction public AvailableCodeAction(CodeAction codeAction, CodeAction parentCodeAction = null) { - if (codeAction == null) - { - throw new ArgumentNullException(nameof(codeAction)); - } - - this.CodeAction = codeAction; + this.CodeAction = codeAction ?? throw new ArgumentNullException(nameof(codeAction)); this.ParentCodeAction = parentCodeAction; } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 8c213387b8..3624659fce 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -29,8 +29,6 @@ public abstract class BaseCodeActionService : IRequestHandl private readonly CodeActionHelper _helper; private readonly MethodInfo _getNestedCodeActions; - private static readonly Func> s_createDiagnosticList = _ => new List(); - protected Lazy> OrderedCodeFixProviders; protected Lazy> OrderedCodeRefactoringProviders; @@ -98,31 +96,16 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText) private async Task CollectCodeFixesActions(Document document, TextSpan span, List codeActions) { - Dictionary> aggregatedDiagnostics = null; - var semanticModel = await document.GetSemanticModelAsync(); - foreach (var diagnostic in semanticModel.GetDiagnostics()) - { - if (!span.IntersectsWith(diagnostic.Location.SourceSpan)) - { - continue; - } - - aggregatedDiagnostics = aggregatedDiagnostics ?? new Dictionary>(); - var list = aggregatedDiagnostics.GetOrAdd(diagnostic.Location.SourceSpan, s_createDiagnosticList); - list.Add(diagnostic); - } + var groupedBySpan = semanticModel.GetDiagnostics() + .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) + .GroupBy(diagnostic => diagnostic.Location.SourceSpan); - if (aggregatedDiagnostics == null) + foreach (var diagnosticGroupedBySpan in groupedBySpan) { - return; - } - - foreach (var kvp in aggregatedDiagnostics) - { - var diagnosticSpan = kvp.Key; - var diagnosticsWithSameSpan = kvp.Value.OrderByDescending(d => d.Severity); + var diagnosticSpan = diagnosticGroupedBySpan.Key; + var diagnosticsWithSameSpan = diagnosticGroupedBySpan.OrderByDescending(d => d.Severity); await AppendFixesAsync(document, diagnosticSpan, diagnosticsWithSameSpan, codeActions); } @@ -133,6 +116,7 @@ private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerabl foreach (var codeFixProvider in OrderedCodeFixProviders.Value) { var fixableDiagnostics = diagnostics.Where(d => HasFix(codeFixProvider, d.Id)).ToImmutableArray(); + if (fixableDiagnostics.Length > 0) { var context = new CodeFixContext(document, span, fixableDiagnostics, (a, _) => codeActions.Add(a), CancellationToken.None); @@ -151,45 +135,31 @@ private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerabl private List GetSortedCodeFixProviders() { - var nodesList = new List>(); - var providerList = new List(); + var codeFixProviders = this.Providers + .SelectMany(provider => provider.CodeFixProviders) + .ToList(); - foreach (var provider in this.Providers) - { - foreach (var codeFixProvider in provider.CodeFixProviders) - { - providerList.Add(codeFixProvider); - nodesList.Add(ProviderNode.From(codeFixProvider)); - } - } + return SortByTopologyIfPossibleOrReturnAsItWas(codeFixProviders); + } - var graph = Graph.GetGraph(nodesList); - if (graph.HasCycles()) - { - return providerList; - } + private List GetSortedCodeRefactoringProviders() + { + var codeRefactoringProviders = this.Providers + .SelectMany(provider => provider.CodeRefactoringProviders) + .ToList(); - return graph.TopologicalSort(); + return SortByTopologyIfPossibleOrReturnAsItWas(codeRefactoringProviders); } - private List GetSortedCodeRefactoringProviders() + private List SortByTopologyIfPossibleOrReturnAsItWas(IEnumerable source) { - var nodesList = new List>(); - var providerList = new List(); + var codeFixNodes = source.Select(codeFix => ProviderNode.From(codeFix)).ToList(); - foreach (var provider in this.Providers) - { - foreach (var codeRefactoringProvider in provider.CodeRefactoringProviders) - { - providerList.Add(codeRefactoringProvider); - nodesList.Add(ProviderNode.From(codeRefactoringProvider)); - } - } + var graph = Graph.GetGraph(codeFixNodes); - var graph = Graph.GetGraph(nodesList); if (graph.HasCycles()) { - return providerList; + return source.ToList(); } return graph.TopologicalSort(); @@ -197,44 +167,18 @@ private List GetSortedCodeRefactoringProviders() private bool HasFix(CodeFixProvider codeFixProvider, string diagnosticId) { - var typeName = codeFixProvider.GetType().FullName; - - if (_helper.IsDisallowed(typeName)) - { - return false; - } - - // TODO: This is a horrible hack! However, remove unnecessary usings only - // responds for diagnostics that are produced by its diagnostic analyzer. - // We need to provide a *real* diagnostic engine to address this. - if (typeName != CodeActionHelper.RemoveUnnecessaryUsingsProviderName) - { - if (!codeFixProvider.FixableDiagnosticIds.Contains(diagnosticId)) - { - return false; - } - } - else if (diagnosticId != "CS8019") // ErrorCode.HDN_UnusedUsingDirective - { - return false; - } - - return true; + return !_helper.IsDisallowed(codeFixProvider.GetType().FullName); } private async Task CollectRefactoringActions(Document document, TextSpan span, List codeActions) { - foreach (var codeRefactoringProvider in OrderedCodeRefactoringProviders.Value) - { - if (_helper.IsDisallowed(codeRefactoringProvider)) - { - continue; - } - - var context = new CodeRefactoringContext(document, span, a => codeActions.Add(a), CancellationToken.None); + var availableRefactorings = OrderedCodeRefactoringProviders.Value.Where(x => !_helper.IsDisallowed(x)); + foreach (var codeRefactoringProvider in availableRefactorings) + { try { + var context = new CodeRefactoringContext(document, span, a => codeActions.Add(a), CancellationToken.None); await codeRefactoringProvider.ComputeRefactoringsAsync(context); } catch (Exception ex) @@ -246,32 +190,17 @@ private async Task CollectRefactoringActions(Document document, TextSpan span, L private IEnumerable ConvertToAvailableCodeAction(IEnumerable actions) { - var codeActions = new List(); - - foreach (var action in actions) + return actions.SelectMany(action => { - var handledNestedActions = false; - - // Roslyn supports "nested" code actions in order to allow submenus in the VS light bulb menu. - // For now, we'll just expand nested code actions in place. var nestedActions = this._getNestedCodeActions.Invoke>(action, null); - if (nestedActions.Length > 0) - { - foreach (var nestedAction in nestedActions) - { - codeActions.Add(new AvailableCodeAction(nestedAction, action)); - } - handledNestedActions = true; - } - - if (!handledNestedActions) + if (nestedActions.Any()) { - codeActions.Add(new AvailableCodeAction(action)); + return nestedActions.Select(nestedAction => new AvailableCodeAction(nestedAction, action)); } - } - return codeActions; + return new[] { new AvailableCodeAction(action) }; + }); } } } From 1e607cc571129ed3e9ef32e2428d36f7d337a0e7 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 29 Jul 2018 08:49:47 +0300 Subject: [PATCH 024/178] Tweaks. --- .../Diagnostics/RoslynAnalyzerService.cs | 32 ++++++++++--------- .../Refactoring/V2/BaseCodeActionService.cs | 14 +++++--- .../Refactoring/V2/GetCodeActionsService.cs | 6 ++-- .../Refactoring/V2/RunCodeActionService.cs | 6 ++-- 4 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index 6053802d7b..fcdbef482f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -20,11 +20,11 @@ public class RoslynAnalyzerService { private readonly ILogger _logger; private ConcurrentDictionary _workQueue = new ConcurrentDictionary(); - private readonly ConcurrentDictionary> _results = - new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _results = + new ConcurrentDictionary>(); private readonly IEnumerable providers; - private int throttlingMs = 1000; + private int throttlingMs = 500; [ImportingConstructor] public RoslynAnalyzerService( @@ -38,7 +38,6 @@ public RoslynAnalyzerService( workspace.WorkspaceChanged += OnWorkspaceChanged; Task.Run(() => Worker(CancellationToken.None)); - Task.Run(() => QueueForAnalysis(workspace.CurrentSolution.Projects)); } private async Task Worker(CancellationToken token) @@ -59,7 +58,7 @@ private async Task Worker(CancellationToken token) .ToList() .ForEach(result => _results[result.Project] = result.Result); - await Task.Delay(500, token); + await Task.Delay(200, token); } } @@ -77,13 +76,18 @@ private IDictionary GetThrottledWork() } } - public IEnumerable GetCurrentDiagnosticResults() => _results.SelectMany(x => x.Value); + public IEnumerable GetCurrentDiagnosticResults() => _results.SelectMany(x => x.Value).Select(x => AsDiagnosticLocation(x)); + public IEnumerable GetCurrentDiagnosticResults2() => _results.SelectMany(x => x.Value); private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { - if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged) + if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged || + changeEvent.Kind == WorkspaceChangeKind.ProjectChanged || + changeEvent.Kind == WorkspaceChangeKind.ProjectAdded || + changeEvent.Kind == WorkspaceChangeKind.ProjectReloaded) { - var project = changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project; + var project = changeEvent.NewSolution.GetProject(changeEvent.ProjectId); + _results.TryRemove(project.Id.ToString(), out _); QueueForAnalysis(new[] { changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project }); } } @@ -96,25 +100,23 @@ private void QueueForAnalysis(IEnumerable projects) }); } - private async Task> Analyze(Project project, CancellationToken token) + private async Task> Analyze(Project project, CancellationToken token) { var allAnalyzers = this.providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); if (!allAnalyzers.Any()) - return ImmutableArray.Empty; + return ImmutableArray.Empty; var compiled = await project.GetCompilationAsync(token); - var analysis = await compiled + return await compiled .WithAnalyzers(allAnalyzers.ToImmutableArray()) .GetAllDiagnosticsAsync(token); - - return analysis.Select(x => AsDiagnosticLocation(x, project)); } - private static DiagnosticLocation AsDiagnosticLocation(Diagnostic diagnostic, Project project) + private static DiagnosticLocation AsDiagnosticLocation(Diagnostic diagnostic) { var span = diagnostic.Location.GetMappedLineSpan(); return new DiagnosticLocation @@ -127,7 +129,7 @@ private static DiagnosticLocation AsDiagnosticLocation(Diagnostic diagnostic, Pr Text = $"{diagnostic.GetMessage()} ({diagnostic.Id})", LogLevel = diagnostic.Severity.ToString(), Id = diagnostic.Id, - Projects = new List { project.Name } + Projects = new List { } }; } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 3624659fce..541e156484 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -15,8 +15,10 @@ using OmniSharp.Mef; using OmniSharp.Models.V2.CodeActions; using OmniSharp.Roslyn.CSharp.Services.CodeActions; +using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Services; using OmniSharp.Utilities; +using System.IO; namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 { @@ -25,18 +27,19 @@ public abstract class BaseCodeActionService : IRequestHandl protected readonly OmniSharpWorkspace Workspace; protected readonly IEnumerable Providers; protected readonly ILogger Logger; - + private readonly RoslynAnalyzerService analyzers; private readonly CodeActionHelper _helper; private readonly MethodInfo _getNestedCodeActions; protected Lazy> OrderedCodeFixProviders; protected Lazy> OrderedCodeRefactoringProviders; - protected BaseCodeActionService(OmniSharpWorkspace workspace, CodeActionHelper helper, IEnumerable providers, ILogger logger) + protected BaseCodeActionService(OmniSharpWorkspace workspace, CodeActionHelper helper, IEnumerable providers, ILogger logger, RoslynAnalyzerService analyzers) { this.Workspace = workspace; this.Providers = providers; this.Logger = logger; + this.analyzers = analyzers; this._helper = helper; OrderedCodeFixProviders = new Lazy>(() => GetSortedCodeFixProviders()); @@ -74,10 +77,12 @@ protected async Task> GetAvailableCodeActions(I await CollectCodeFixesActions(document, span, codeActions); await CollectRefactoringActions(document, span, codeActions); + var distinctActions = codeActions.GroupBy(x => x.Title).Select(x => x.First()); + // Be sure to filter out any code actions that inherit from CodeActionWithOptions. // This isn't a great solution and might need changing later, but every Roslyn code action // derived from this type tries to display a dialog. For now, this is a reasonable solution. - var availableActions = ConvertToAvailableCodeAction(codeActions) + var availableActions = ConvertToAvailableCodeAction(distinctActions) .Where(a => !a.CodeAction.GetType().GetTypeInfo().IsSubclassOf(typeof(CodeActionWithOptions))); return availableActions; @@ -99,6 +104,7 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis var semanticModel = await document.GetSemanticModelAsync(); var groupedBySpan = semanticModel.GetDiagnostics() + .Concat(this.analyzers.GetCurrentDiagnosticResults2()) .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) .GroupBy(diagnostic => diagnostic.Location.SourceSpan); @@ -167,7 +173,7 @@ private List SortByTopologyIfPossibleOrReturnAsItWas(IEnumerable source private bool HasFix(CodeFixProvider codeFixProvider, string diagnosticId) { - return !_helper.IsDisallowed(codeFixProvider.GetType().FullName); + return !_helper.IsDisallowed(codeFixProvider.GetType().FullName) && codeFixProvider.FixableDiagnosticIds.Any(id => id == diagnosticId); } private async Task CollectRefactoringActions(Document document, TextSpan span, List codeActions) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs index efc47cdb3a..4682fd40e9 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs @@ -7,6 +7,7 @@ using OmniSharp.Mef; using OmniSharp.Models.V2.CodeActions; using OmniSharp.Roslyn.CSharp.Services.CodeActions; +using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Services; namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 @@ -19,8 +20,9 @@ public GetCodeActionsService( OmniSharpWorkspace workspace, CodeActionHelper helper, [ImportMany] IEnumerable providers, - ILoggerFactory loggerFactory) - : base(workspace, helper, providers, loggerFactory.CreateLogger()) + ILoggerFactory loggerFactory, + RoslynAnalyzerService analyzers) + : base(workspace, helper, providers, loggerFactory.CreateLogger(), analyzers) { } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index d98c7bef78..6187e9292e 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -12,6 +12,7 @@ using OmniSharp.Mef; using OmniSharp.Models; using OmniSharp.Roslyn.CSharp.Services.CodeActions; +using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Roslyn.Utilities; using OmniSharp.Services; using OmniSharp.Utilities; @@ -38,8 +39,9 @@ public RunCodeActionService( OmniSharpWorkspace workspace, CodeActionHelper helper, [ImportMany] IEnumerable providers, - ILoggerFactory loggerFactory) - : base(workspace, helper, providers, loggerFactory.CreateLogger()) + ILoggerFactory loggerFactory, + RoslynAnalyzerService analyzers) + : base(workspace, helper, providers, loggerFactory.CreateLogger(), analyzers) { _loader = loader; _workspaceAssembly = _loader.LazyLoad(Configuration.RoslynWorkspaces); From 21f91c397eb817586d6c088875267889d6847dcd Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 29 Jul 2018 13:55:13 +0300 Subject: [PATCH 025/178] Refactoring and tweaks. --- .../Helpers/DiagnosticExtensions.cs | 2 +- .../Services/Diagnostics/CodeCheckService.cs | 2 +- .../Diagnostics/RoslynAnalyzerService.cs | 42 +++++++------------ .../Refactoring/V2/BaseCodeActionService.cs | 2 +- 4 files changed, 18 insertions(+), 30 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs index a2b89de94d..e1d891301d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs +++ b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs @@ -18,7 +18,7 @@ internal static DiagnosticLocation ToDiagnosticLocation(this Diagnostic diagnost Column = span.StartLinePosition.Character, EndLine = span.EndLinePosition.Line, EndColumn = span.EndLinePosition.Character, - Text = diagnostic.GetMessage(), + Text = $"{diagnostic.GetMessage()} ({diagnostic.Id})", LogLevel = diagnostic.Severity.ToString(), Id = diagnostic.Id }; diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index f0e8d69f8e..d22b00cd27 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -31,7 +31,7 @@ public async Task Handle(CodeCheckRequest request) var quickFixes = await documents.FindDiagnosticLocationsAsync(); var analyzerResults = - _roslynAnalyzer.GetCurrentDiagnosticResults().Where(x => + _roslynAnalyzer.GetCurrentDiagnosticResult().Select(x => x.ToDiagnosticLocation()).Where(x => request.FileName == null || x.FileName == request.FileName); return new QuickFixResponse(quickFixes.Concat(analyzerResults)); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs index fcdbef482f..671c188761 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs @@ -9,7 +9,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Extensions.Logging; -using OmniSharp.Models.Diagnostics; using OmniSharp.Services; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics @@ -19,12 +18,12 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class RoslynAnalyzerService { private readonly ILogger _logger; - private ConcurrentDictionary _workQueue = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = new ConcurrentDictionary(); private readonly ConcurrentDictionary> _results = new ConcurrentDictionary>(); private readonly IEnumerable providers; - private int throttlingMs = 500; + private readonly int throttlingMs = 500; [ImportingConstructor] public RoslynAnalyzerService( @@ -37,7 +36,12 @@ public RoslynAnalyzerService( workspace.WorkspaceChanged += OnWorkspaceChanged; - Task.Run(() => Worker(CancellationToken.None)); + Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); + Task.Run(() => + { + while (!workspace.Initialized) Task.Delay(500); + QueueForAnalysis(workspace.CurrentSolution.Projects); + }); } private async Task Worker(CancellationToken token) @@ -68,6 +72,8 @@ private IDictionary GetThrottledWork() { var currentWork = _workQueue .Where(x => x.Value.modified.AddMilliseconds(this.throttlingMs) < DateTime.UtcNow) + .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. + .Take(3) // Limit mount of work executed by once. This is needed on large solution... .ToList(); currentWork.Select(x => x.Key).ToList().ForEach(key => _workQueue.TryRemove(key, out _)); @@ -76,8 +82,7 @@ private IDictionary GetThrottledWork() } } - public IEnumerable GetCurrentDiagnosticResults() => _results.SelectMany(x => x.Value).Select(x => AsDiagnosticLocation(x)); - public IEnumerable GetCurrentDiagnosticResults2() => _results.SelectMany(x => x.Value); + public IEnumerable GetCurrentDiagnosticResult() => _results.SelectMany(x => x.Value); private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { @@ -87,17 +92,17 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv changeEvent.Kind == WorkspaceChangeKind.ProjectReloaded) { var project = changeEvent.NewSolution.GetProject(changeEvent.ProjectId); + _results.TryRemove(project.Id.ToString(), out _); + QueueForAnalysis(new[] { changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project }); } } private void QueueForAnalysis(IEnumerable projects) { - projects.ToList().ForEach(project => - { - _workQueue.AddOrUpdate(project.Id.ToString(), (modified: DateTime.UtcNow, project: project), (key, old) => (modified: DateTime.UtcNow, project: project)); - }); + projects.ToList() + .ForEach(project => _workQueue.AddOrUpdate(project.Id.ToString(), (modified: DateTime.UtcNow, project: project), (key, old) => (modified: DateTime.UtcNow, project: project))); } private async Task> Analyze(Project project, CancellationToken token) @@ -115,22 +120,5 @@ private async Task> Analyze(Project project, Cancellatio .WithAnalyzers(allAnalyzers.ToImmutableArray()) .GetAllDiagnosticsAsync(token); } - - private static DiagnosticLocation AsDiagnosticLocation(Diagnostic diagnostic) - { - var span = diagnostic.Location.GetMappedLineSpan(); - return new DiagnosticLocation - { - FileName = span.Path, - Line = span.StartLinePosition.Line, - Column = span.StartLinePosition.Character, - EndLine = span.EndLinePosition.Line, - EndColumn = span.EndLinePosition.Character, - Text = $"{diagnostic.GetMessage()} ({diagnostic.Id})", - LogLevel = diagnostic.Severity.ToString(), - Id = diagnostic.Id, - Projects = new List { } - }; - } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 541e156484..53921b6898 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -104,7 +104,7 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis var semanticModel = await document.GetSemanticModelAsync(); var groupedBySpan = semanticModel.GetDiagnostics() - .Concat(this.analyzers.GetCurrentDiagnosticResults2()) + .Concat(this.analyzers.GetCurrentDiagnosticResult()) .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) .GroupBy(diagnostic => diagnostic.Location.SourceSpan); From b60d5b0e2adebabd68cc14ec7b9c9a5464515c7e Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 29 Jul 2018 15:46:34 +0300 Subject: [PATCH 026/178] Added support for remove unnecessary usings. --- .../Services/Refactoring/V2/BaseCodeActionService.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 53921b6898..af1f60e151 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -34,6 +34,12 @@ public abstract class BaseCodeActionService : IRequestHandl protected Lazy> OrderedCodeFixProviders; protected Lazy> OrderedCodeRefactoringProviders; + // For some (probably visual studio specific?) reason diagnostic and fix codes doesn't match in every case. + private Dictionary customDiagVsFixMap = new Dictionary + { + { "CS8019", "RemoveUnnecessaryImportsFixable" } + }; + protected BaseCodeActionService(OmniSharpWorkspace workspace, CodeActionHelper helper, IEnumerable providers, ILogger logger, RoslynAnalyzerService analyzers) { this.Workspace = workspace; @@ -173,12 +179,13 @@ private List SortByTopologyIfPossibleOrReturnAsItWas(IEnumerable source private bool HasFix(CodeFixProvider codeFixProvider, string diagnosticId) { - return !_helper.IsDisallowed(codeFixProvider.GetType().FullName) && codeFixProvider.FixableDiagnosticIds.Any(id => id == diagnosticId); + return codeFixProvider.FixableDiagnosticIds.Any(id => id == diagnosticId) + || (customDiagVsFixMap.ContainsKey(diagnosticId) && codeFixProvider.FixableDiagnosticIds.Any(id => id == customDiagVsFixMap[diagnosticId])); } private async Task CollectRefactoringActions(Document document, TextSpan span, List codeActions) { - var availableRefactorings = OrderedCodeRefactoringProviders.Value.Where(x => !_helper.IsDisallowed(x)); + var availableRefactorings = OrderedCodeRefactoringProviders.Value; foreach (var codeRefactoringProvider in availableRefactorings) { From 8cc9ad29f5f8b0d3350f59ec9217a83df18c0ef5 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 29 Jul 2018 19:58:43 +0300 Subject: [PATCH 027/178] Implemented code fixes support by project analyzer references. --- .../HttpCommandLineApplication.cs | 1 - src/OmniSharp.MSBuild/ProjectManager.cs | 10 ++- src/OmniSharp.MSBuild/ProjectSystem.cs | 9 ++- .../Services/Diagnostics/CodeCheckService.cs | 2 +- .../Refactoring/V2/BaseCodeActionService.cs | 14 ++-- .../Refactoring/V2/CodeFixReference.cs | 66 +++++++++++++++++++ .../Refactoring/V2/GetCodeActionsService.cs | 5 +- .../Refactoring/V2/RunCodeActionService.cs | 5 +- .../Diagnostics/RoslynAnalyzerService.cs | 30 ++++++--- 9 files changed, 114 insertions(+), 28 deletions(-) create mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixReference.cs rename src/OmniSharp.Roslyn.CSharp/{Services => Workers}/Diagnostics/RoslynAnalyzerService.cs (76%) diff --git a/src/OmniSharp.Http/HttpCommandLineApplication.cs b/src/OmniSharp.Http/HttpCommandLineApplication.cs index fea8c8bfdc..b76bb7e327 100644 --- a/src/OmniSharp.Http/HttpCommandLineApplication.cs +++ b/src/OmniSharp.Http/HttpCommandLineApplication.cs @@ -14,7 +14,6 @@ public HttpCommandLineApplication() : base() _serverInterface = Application.Option("-i | --interface", "Server interface address (defaults to 'localhost').", CommandOptionType.SingleValue); } - public int Port => _port.GetValueOrDefault(2000); public string Interface => _serverInterface.GetValueOrDefault("localhost"); } diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 673eb5bdb1..0919797120 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -16,6 +16,7 @@ using OmniSharp.MSBuild.Logging; using OmniSharp.MSBuild.Models.Events; using OmniSharp.MSBuild.ProjectFile; +using OmniSharp.Roslyn.CSharp.Services.Refactoring.V2; using OmniSharp.Roslyn.Utilities; using OmniSharp.Services; using OmniSharp.Utilities; @@ -45,7 +46,7 @@ public ProjectToUpdate(string filePath, bool allowAutoRestore) private readonly HashSet _failedToLoadProjectFiles; private readonly ProjectLoader _projectLoader; private readonly OmniSharpWorkspace _workspace; - + private readonly CodeFixesForProjects codeFixesForProject; private const int LoopDelay = 100; // milliseconds private readonly BufferBlock _queue; private readonly CancellationTokenSource _processLoopCancellation; @@ -54,7 +55,7 @@ public ProjectToUpdate(string filePath, bool allowAutoRestore) private readonly FileSystemNotificationCallback _onDirectoryFileChanged; - public ProjectManager(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, IFileSystemWatcher fileSystemWatcher, MetadataFileReferenceCache metadataFileReferenceCache, PackageDependencyChecker packageDependencyChecker, ProjectLoader projectLoader, OmniSharpWorkspace workspace) + public ProjectManager(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, IFileSystemWatcher fileSystemWatcher, MetadataFileReferenceCache metadataFileReferenceCache, PackageDependencyChecker packageDependencyChecker, ProjectLoader projectLoader, OmniSharpWorkspace workspace, CodeFixesForProjects codeFixesForProject) { _logger = loggerFactory.CreateLogger(); _eventEmitter = eventEmitter; @@ -65,7 +66,7 @@ public ProjectManager(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, _failedToLoadProjectFiles = new HashSet(StringComparer.OrdinalIgnoreCase); _projectLoader = projectLoader; _workspace = workspace; - + this.codeFixesForProject = codeFixesForProject; _queue = new BufferBlock(); _processLoopCancellation = new CancellationTokenSource(); _processLoopTask = Task.Run(() => ProcessLoopAsync(_processLoopCancellation.Token)); @@ -264,6 +265,9 @@ private void AddProject(ProjectFileInfo projectFileInfo) _projectFiles.Add(projectFileInfo); var projectInfo = projectFileInfo.CreateProjectInfo(); + + codeFixesForProject.LoadFrom(projectInfo.Id.ToString(), projectFileInfo.Analyzers); + var newSolution = _workspace.CurrentSolution.AddProject(projectInfo); if (!_workspace.TryApplyChanges(newSolution)) diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 4d432acf18..ec32635943 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -16,6 +16,7 @@ using OmniSharp.MSBuild.ProjectFile; using OmniSharp.MSBuild.SolutionParsing; using OmniSharp.Options; +using OmniSharp.Roslyn.CSharp.Services.Refactoring.V2; using OmniSharp.Services; namespace OmniSharp.MSBuild @@ -33,6 +34,7 @@ public class ProjectSystem : IProjectSystem private readonly IFileSystemWatcher _fileSystemWatcher; private readonly FileSystemHelper _fileSystemHelper; private readonly ILoggerFactory _loggerFactory; + private readonly CodeFixesForProjects codeFixesForProjects; private readonly ILogger _logger; private readonly object _gate = new object(); @@ -60,7 +62,8 @@ public ProjectSystem( IEventEmitter eventEmitter, IFileSystemWatcher fileSystemWatcher, FileSystemHelper fileSystemHelper, - ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + CodeFixesForProjects codeFixesForProjects) { _environment = environment; _workspace = workspace; @@ -72,7 +75,7 @@ public ProjectSystem( _fileSystemWatcher = fileSystemWatcher; _fileSystemHelper = fileSystemHelper; _loggerFactory = loggerFactory; - + this.codeFixesForProjects = codeFixesForProjects; _projectsToProcess = new Queue(); _logger = loggerFactory.CreateLogger(); } @@ -93,7 +96,7 @@ public void Initalize(IConfiguration configuration) _packageDependencyChecker = new PackageDependencyChecker(_loggerFactory, _eventEmitter, _dotNetCli, _options); _loader = new ProjectLoader(_options, _environment.TargetDirectory, _propertyOverrides, _loggerFactory, _sdksPathResolver); - _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace); + _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace, codeFixesForProjects); var initialProjectPaths = GetInitialProjectPaths(); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index d22b00cd27..00c8a0437d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -12,7 +12,7 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics [OmniSharpHandler(OmniSharpEndpoints.CodeCheck, LanguageNames.CSharp)] public class CodeCheckService : IRequestHandler { - private OmniSharpWorkspace _workspace; + private readonly OmniSharpWorkspace _workspace; private readonly RoslynAnalyzerService _roslynAnalyzer; [ImportingConstructor] diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index af1f60e151..9aecb0e10c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -28,10 +28,9 @@ public abstract class BaseCodeActionService : IRequestHandl protected readonly IEnumerable Providers; protected readonly ILogger Logger; private readonly RoslynAnalyzerService analyzers; - private readonly CodeActionHelper _helper; + private readonly CodeFixesForProjects codeFixesForProject; private readonly MethodInfo _getNestedCodeActions; - protected Lazy> OrderedCodeFixProviders; protected Lazy> OrderedCodeRefactoringProviders; // For some (probably visual studio specific?) reason diagnostic and fix codes doesn't match in every case. @@ -40,15 +39,14 @@ public abstract class BaseCodeActionService : IRequestHandl { "CS8019", "RemoveUnnecessaryImportsFixable" } }; - protected BaseCodeActionService(OmniSharpWorkspace workspace, CodeActionHelper helper, IEnumerable providers, ILogger logger, RoslynAnalyzerService analyzers) + protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, RoslynAnalyzerService analyzers, CodeFixesForProjects codeFixesForProject) { this.Workspace = workspace; this.Providers = providers; this.Logger = logger; this.analyzers = analyzers; - this._helper = helper; + this.codeFixesForProject = codeFixesForProject; - OrderedCodeFixProviders = new Lazy>(() => GetSortedCodeFixProviders()); OrderedCodeRefactoringProviders = new Lazy>(() => GetSortedCodeRefactoringProviders()); // Sadly, the CodeAction.NestedCodeActions property is still internal. @@ -125,7 +123,7 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerable diagnostics, List codeActions) { - foreach (var codeFixProvider in OrderedCodeFixProviders.Value) + foreach (var codeFixProvider in GetSortedCodeFixProviders(document)) { var fixableDiagnostics = diagnostics.Where(d => HasFix(codeFixProvider, d.Id)).ToImmutableArray(); @@ -145,11 +143,11 @@ private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerabl } } - private List GetSortedCodeFixProviders() + private List GetSortedCodeFixProviders(Document document) { var codeFixProviders = this.Providers .SelectMany(provider => provider.CodeFixProviders) - .ToList(); + .Concat(codeFixesForProject.GetAllCodeFixesForProject(document.Project.Id.ToString())); return SortByTopologyIfPossibleOrReturnAsItWas(codeFixProviders); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixReference.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixReference.cs new file mode 100644 index 0000000000..9295ae2676 --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixReference.cs @@ -0,0 +1,66 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Composition; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 +{ + [Shared] + [Export(typeof(CodeFixesForProjects))] + public class CodeFixesForProjects + { + private readonly ConcurrentDictionary> codeFixCache = new ConcurrentDictionary>(); + + [ImportingConstructor] + public CodeFixesForProjects() + { + } + + public IEnumerable GetAllCodeFixesForProject(string projectId) + { + if (codeFixCache.ContainsKey(projectId)) + return codeFixCache[projectId]; + return Enumerable.Empty(); + } + + public void LoadFrom(string projectId, IEnumerable AnalyzerPaths) + { + var codeFixes = AnalyzerPaths + .Where(x => x.EndsWith("CodeFixes.dll")) + .SelectMany(codeFixDllPath => + { + var loadedAssembly = Assembly.LoadFrom(codeFixDllPath); + var validTypes = loadedAssembly.GetTypes() + .Where(type => !type.GetTypeInfo().IsInterface && !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().ContainsGenericParameters) + .Where(t => typeof(CodeFixProvider).IsAssignableFrom(t)); + + return validTypes + .Select(type => CreateInstance(type)) + .Where(instance => instance != null); + }); + + codeFixCache.AddOrUpdate(projectId, codeFixes, (_, __) => codeFixes); + } + + private static T CreateInstance(Type type) where T : class + { + try + { + var defaultCtor = type.GetConstructor(new Type[] { }); + + return defaultCtor != null + ? (T)Activator.CreateInstance(type) + : null; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to create instrance of {type.FullName} in {type.AssemblyQualifiedName}.", ex); + } + } + } +} diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs index 4682fd40e9..659a787f68 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs @@ -21,8 +21,9 @@ public GetCodeActionsService( CodeActionHelper helper, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - RoslynAnalyzerService analyzers) - : base(workspace, helper, providers, loggerFactory.CreateLogger(), analyzers) + RoslynAnalyzerService analyzers, + CodeFixesForProjects codeFixesForProjects) + : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) { } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index 6187e9292e..36e5113759 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -40,8 +40,9 @@ public RunCodeActionService( CodeActionHelper helper, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - RoslynAnalyzerService analyzers) - : base(workspace, helper, providers, loggerFactory.CreateLogger(), analyzers) + RoslynAnalyzerService analyzers, + CodeFixesForProjects codeFixesForProjects) + : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) { _loader = loader; _workspaceAssembly = _loader.LazyLoad(Configuration.RoslynWorkspaces); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs similarity index 76% rename from src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs rename to src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 671c188761..e8ec61fb0e 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -18,9 +18,12 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class RoslynAnalyzerService { private readonly ILogger _logger; + private readonly ConcurrentDictionary _workQueue = new ConcurrentDictionary(); + private readonly ConcurrentDictionary> _results = new ConcurrentDictionary>(); + private readonly IEnumerable providers; private readonly int throttlingMs = 500; @@ -86,23 +89,34 @@ private IDictionary GetThrottledWork() private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { - if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged || - changeEvent.Kind == WorkspaceChangeKind.ProjectChanged || - changeEvent.Kind == WorkspaceChangeKind.ProjectAdded || - changeEvent.Kind == WorkspaceChangeKind.ProjectReloaded) + if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged + || changeEvent.Kind == WorkspaceChangeKind.ProjectChanged + || changeEvent.Kind == WorkspaceChangeKind.ProjectAdded + || changeEvent.Kind == WorkspaceChangeKind.ProjectReloaded) { var project = changeEvent.NewSolution.GetProject(changeEvent.ProjectId); + ClearCurrentlyEditedFileFromAnalysisIfApplicaple(changeEvent, project); + QueueForAnalysis(new[] { project }); + } + } - _results.TryRemove(project.Id.ToString(), out _); - - QueueForAnalysis(new[] { changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).Project }); + // This isn't perfect, but if you make change that add lines etc. for litle moment analyzers only knowns analysis from original file + // which can cause warnings in incorrect locations if editor fetches them at that point. For this reason during analysis don't return + // any information about that file before new result is available. + private void ClearCurrentlyEditedFileFromAnalysisIfApplicaple(WorkspaceChangeEventArgs changeEvent, Project project) + { + if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged && _results.ContainsKey(project.Id.ToString())) + { + var updatedFilePath = changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).FilePath; + var filteredResults = _results[project.Id.ToString()].Where(x => x.Location.GetMappedLineSpan().Path != updatedFilePath); + _results.AddOrUpdate(project.Id.ToString(), filteredResults, (_, __) => filteredResults); } } private void QueueForAnalysis(IEnumerable projects) { projects.ToList() - .ForEach(project => _workQueue.AddOrUpdate(project.Id.ToString(), (modified: DateTime.UtcNow, project: project), (key, old) => (modified: DateTime.UtcNow, project: project))); + .ForEach(project => _workQueue.AddOrUpdate(project.Id.ToString(), (modified: DateTime.UtcNow, project: project), (_, __) => (modified: DateTime.UtcNow, project: project))); } private async Task> Analyze(Project project, CancellationToken token) From bfe05adf34ce38c4da459d58056d11e32964e4b0 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 30 Jul 2018 09:02:52 +0300 Subject: [PATCH 028/178] Made semantic analysis result distinct. --- .../Services/Diagnostics/CodeCheckService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 00c8a0437d..f6e8254409 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -34,7 +34,9 @@ public async Task Handle(CodeCheckRequest request) _roslynAnalyzer.GetCurrentDiagnosticResult().Select(x => x.ToDiagnosticLocation()).Where(x => request.FileName == null || x.FileName == request.FileName); - return new QuickFixResponse(quickFixes.Concat(analyzerResults)); + var distinctDiagnosticResult = quickFixes.Concat(analyzerResults).GroupBy(x => x.Id).Select(x => x.First()); + + return new QuickFixResponse(distinctDiagnosticResult); } } } From 371271d329d02a251ecd5360e62c650a9d67d412 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 30 Jul 2018 10:33:53 +0300 Subject: [PATCH 029/178] Update to distinct algorithm. --- .../Services/Diagnostics/CodeCheckService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index f6e8254409..f63edc0d41 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -34,7 +34,7 @@ public async Task Handle(CodeCheckRequest request) _roslynAnalyzer.GetCurrentDiagnosticResult().Select(x => x.ToDiagnosticLocation()).Where(x => request.FileName == null || x.FileName == request.FileName); - var distinctDiagnosticResult = quickFixes.Concat(analyzerResults).GroupBy(x => x.Id).Select(x => x.First()); + var distinctDiagnosticResult = quickFixes.Concat(analyzerResults).GroupBy(x => new { x.Id, x.FileName}).Select(x => x.First()); return new QuickFixResponse(distinctDiagnosticResult); } From 7ed7702f2d30bf2dbc7afbfb50e7b3b1ae03a21b Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 30 Jul 2018 13:14:03 +0300 Subject: [PATCH 030/178] Improved version but still has issues with larger projects. --- .../Services/Diagnostics/CodeCheckService.cs | 38 ++++++++++++++----- .../Refactoring/V2/BaseCodeActionService.cs | 5 ++- .../Diagnostics/RoslynAnalyzerService.cs | 17 ++++++++- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index f63edc0d41..a67054b2a9 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -6,6 +6,8 @@ using OmniSharp.Mef; using OmniSharp.Models; using OmniSharp.Models.CodeCheck; +using System.Collections.Generic; +using OmniSharp.Models.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { @@ -24,19 +26,37 @@ public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService rosl public async Task Handle(CodeCheckRequest request) { - var documents = request.FileName != null - ? _workspace.GetDocuments(request.FileName) - : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); - - var quickFixes = await documents.FindDiagnosticLocationsAsync(); + var projects = request.FileName != null + ? _workspace.GetDocuments(request.FileName).Select(x => x.Project) + : _workspace.CurrentSolution.Projects; var analyzerResults = - _roslynAnalyzer.GetCurrentDiagnosticResult().Select(x => x.ToDiagnosticLocation()).Where(x => - request.FileName == null || x.FileName == request.FileName); + await _roslynAnalyzer.GetCurrentDiagnosticResult(projects.Select(x => x.Id)); - var distinctDiagnosticResult = quickFixes.Concat(analyzerResults).GroupBy(x => new { x.Id, x.FileName}).Select(x => x.First()); + return new QuickFixResponse(analyzerResults + .SelectMany(x => x.Value, (key, diagnostic) => new { key, diagnostic }).Select(x => + { + var asLocation = x.diagnostic.ToDiagnosticLocation(); + asLocation.Projects = new[] { x.key.Key }; + return asLocation; + }).Where(x => request.FileName == null || x.FileName == request.FileName) + .Take(50)); + } - return new QuickFixResponse(distinctDiagnosticResult); + private static DiagnosticLocation ToDiagnosticLocation(Diagnostic diagnostic, string project) + { + var span = diagnostic.Location.GetMappedLineSpan(); + return new DiagnosticLocation + { + FileName = span.Path, + Line = span.StartLinePosition.Line, + Column = span.StartLinePosition.Character, + EndLine = span.EndLinePosition.Line, + EndColumn = span.EndLinePosition.Character, + Text = $"{diagnostic.GetMessage()} ({diagnostic.Id})", + LogLevel = diagnostic.Severity.ToString(), + Id = diagnostic.Id + }; } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 9aecb0e10c..074297f562 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -34,7 +34,7 @@ public abstract class BaseCodeActionService : IRequestHandl protected Lazy> OrderedCodeRefactoringProviders; // For some (probably visual studio specific?) reason diagnostic and fix codes doesn't match in every case. - private Dictionary customDiagVsFixMap = new Dictionary + private readonly Dictionary customDiagVsFixMap = new Dictionary { { "CS8019", "RemoveUnnecessaryImportsFixable" } }; @@ -57,6 +57,7 @@ protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable x.Value)) .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) .GroupBy(diagnostic => diagnostic.Location.SourceSpan); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index e8ec61fb0e..5e2d70b9e4 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -27,12 +27,14 @@ public class RoslynAnalyzerService private readonly IEnumerable providers; private readonly int throttlingMs = 500; + private readonly DiagnosticEventForwarder _forwarder; [ImportingConstructor] public RoslynAnalyzerService( OmniSharpWorkspace workspace, [ImportMany] IEnumerable providers, - ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + DiagnosticEventForwarder forwarder) { _logger = loggerFactory.CreateLogger(); this.providers = providers; @@ -44,7 +46,9 @@ public RoslynAnalyzerService( { while (!workspace.Initialized) Task.Delay(500); QueueForAnalysis(workspace.CurrentSolution.Projects); + _logger.LogInformation("Solution updated, requed all projects for code analysis."); }); + _forwarder = forwarder; } private async Task Worker(CancellationToken token) @@ -85,7 +89,16 @@ private IDictionary GetThrottledWork() } } - public IEnumerable GetCurrentDiagnosticResult() => _results.SelectMany(x => x.Value); + public IDictionary> GetCurrentDiagnosticResult() => _results.ToDictionary(x => x.Key, x => x.Value); + + public Task>> GetCurrentDiagnosticResult(IEnumerable projectIds) + { + return Task.Run(() => + { + while(projectIds.Any(projectId => _workQueue.ContainsKey(projectId.ToString()))) Task.Delay(100); + return _results.ToDictionary(x => x.Key, x => x.Value); + }); + } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { From b51d8f5c9394aaf516c88d9d9fe42416efeeb9a1 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 30 Jul 2018 20:18:31 +0300 Subject: [PATCH 031/178] Fixes for larger project (like omnisharp). --- .../Services/Diagnostics/CodeCheckService.cs | 34 +++---- .../Refactoring/V2/BaseCodeActionService.cs | 4 +- .../Diagnostics/RoslynAnalyzerService.cs | 95 +++++++++---------- 3 files changed, 61 insertions(+), 72 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index a67054b2a9..817a700cd9 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -8,6 +8,7 @@ using OmniSharp.Models.CodeCheck; using System.Collections.Generic; using OmniSharp.Models.Diagnostics; +using Microsoft.Extensions.Logging; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { @@ -16,12 +17,14 @@ public class CodeCheckService : IRequestHandler _logger; [ImportingConstructor] - public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService roslynAnalyzer) + public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService roslynAnalyzer, ILoggerFactory loggerFactory) { _workspace = workspace; _roslynAnalyzer = roslynAnalyzer; + _logger = loggerFactory.CreateLogger(); } public async Task Handle(CodeCheckRequest request) @@ -34,29 +37,14 @@ public async Task Handle(CodeCheckRequest request) await _roslynAnalyzer.GetCurrentDiagnosticResult(projects.Select(x => x.Id)); return new QuickFixResponse(analyzerResults - .SelectMany(x => x.Value, (key, diagnostic) => new { key, diagnostic }).Select(x => + .Where(x => (request.FileName == null || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) + .Select(x => { - var asLocation = x.diagnostic.ToDiagnosticLocation(); - asLocation.Projects = new[] { x.key.Key }; - return asLocation; - }).Where(x => request.FileName == null || x.FileName == request.FileName) - .Take(50)); - } - - private static DiagnosticLocation ToDiagnosticLocation(Diagnostic diagnostic, string project) - { - var span = diagnostic.Location.GetMappedLineSpan(); - return new DiagnosticLocation - { - FileName = span.Path, - Line = span.StartLinePosition.Line, - Column = span.StartLinePosition.Character, - EndLine = span.EndLinePosition.Line, - EndColumn = span.EndLinePosition.Character, - Text = $"{diagnostic.GetMessage()} ({diagnostic.Id})", - LogLevel = diagnostic.Severity.ToString(), - Id = diagnostic.Id - }; + var asLocation = x.diagnostic.ToDiagnosticLocation(); + asLocation.Projects = new[] { x.name }; + return asLocation; + }) + .Where(x => x.FileName != null)); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 074297f562..0be47c8256 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -108,8 +108,10 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis { var semanticModel = await document.GetSemanticModelAsync(); + var analyzers = await this.analyzers.GetCurrentDiagnosticResult(new [] { document.Project.Id }); + var groupedBySpan = semanticModel.GetDiagnostics() - .Concat(this.analyzers.GetCurrentDiagnosticResult().SelectMany(x => x.Value)) + .Concat(analyzers.Select(x => x.diagnostic)) .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) .GroupBy(diagnostic => diagnostic.Location.SourceSpan); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 5e2d70b9e4..9750e472ba 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -19,15 +19,17 @@ public class RoslynAnalyzerService { private readonly ILogger _logger; - private readonly ConcurrentDictionary _workQueue = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = new ConcurrentDictionary(); - private readonly ConcurrentDictionary> _results = - new ConcurrentDictionary>(); + private readonly ConcurrentDictionary diagnostics)> _results = + new ConcurrentDictionary diagnostics)>(); - private readonly IEnumerable providers; + private readonly IEnumerable _providers; private readonly int throttlingMs = 500; + private readonly DiagnosticEventForwarder _forwarder; + private readonly OmniSharpWorkspace _workspace; [ImportingConstructor] public RoslynAnalyzerService( @@ -37,43 +39,54 @@ public RoslynAnalyzerService( DiagnosticEventForwarder forwarder) { _logger = loggerFactory.CreateLogger(); - this.providers = providers; + _providers = providers; workspace.WorkspaceChanged += OnWorkspaceChanged; Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); + Task.Run(() => { while (!workspace.Initialized) Task.Delay(500); - QueueForAnalysis(workspace.CurrentSolution.Projects); - _logger.LogInformation("Solution updated, requed all projects for code analysis."); + QueueForAnalysis(workspace.CurrentSolution.Projects.ToList()); + _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); }); + _forwarder = forwarder; + _workspace = workspace; } private async Task Worker(CancellationToken token) { while (!token.IsCancellationRequested) { - var currentWork = GetThrottledWork(); - - var analyzerResults = await Task - .WhenAll(currentWork - .Select(async x => new - { - Project = x.Key, - Result = await Analyze(x.Value, token) - })); - - analyzerResults - .ToList() - .ForEach(result => _results[result.Project] = result.Result); - - await Task.Delay(200, token); + try + { + var currentWork = GetThrottledWork(); + + var analyzerResults = await Task + .WhenAll(currentWork + .Select(async x => new + { + ProjectId = x.Value.Id, + ProjectName = x.Value.Name, + Result = await Analyze(x.Value, token) + })); + + analyzerResults + .ToList() + .ForEach(result => _results[result.ProjectId] = (result.ProjectName, result.Result)); + + await Task.Delay(200, token); + } + catch (Exception ex) + { + _logger.LogError($"Analyzer worker failed: {ex}"); + } } } - private IDictionary GetThrottledWork() + private IDictionary GetThrottledWork() { lock (_workQueue) { @@ -89,52 +102,38 @@ private IDictionary GetThrottledWork() } } - public IDictionary> GetCurrentDiagnosticResult() => _results.ToDictionary(x => x.Key, x => x.Value); - - public Task>> GetCurrentDiagnosticResult(IEnumerable projectIds) + public Task> GetCurrentDiagnosticResult(IEnumerable projectIds) { return Task.Run(() => { - while(projectIds.Any(projectId => _workQueue.ContainsKey(projectId.ToString()))) Task.Delay(100); - return _results.ToDictionary(x => x.Key, x => x.Value); + while (projectIds.Any(projectId => _workQueue.ContainsKey(projectId))) + { + Task.Delay(100); + } + return _results + .Where(x => projectIds.Any(pid => pid == x.Key)) + .SelectMany(x => x.Value.diagnostics, (k, v) => (k.Value.name, v)); }); } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { - if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged - || changeEvent.Kind == WorkspaceChangeKind.ProjectChanged - || changeEvent.Kind == WorkspaceChangeKind.ProjectAdded - || changeEvent.Kind == WorkspaceChangeKind.ProjectReloaded) + if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged) { var project = changeEvent.NewSolution.GetProject(changeEvent.ProjectId); - ClearCurrentlyEditedFileFromAnalysisIfApplicaple(changeEvent, project); QueueForAnalysis(new[] { project }); } } - // This isn't perfect, but if you make change that add lines etc. for litle moment analyzers only knowns analysis from original file - // which can cause warnings in incorrect locations if editor fetches them at that point. For this reason during analysis don't return - // any information about that file before new result is available. - private void ClearCurrentlyEditedFileFromAnalysisIfApplicaple(WorkspaceChangeEventArgs changeEvent, Project project) - { - if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged && _results.ContainsKey(project.Id.ToString())) - { - var updatedFilePath = changeEvent.NewSolution.GetDocument(changeEvent.DocumentId).FilePath; - var filteredResults = _results[project.Id.ToString()].Where(x => x.Location.GetMappedLineSpan().Path != updatedFilePath); - _results.AddOrUpdate(project.Id.ToString(), filteredResults, (_, __) => filteredResults); - } - } - private void QueueForAnalysis(IEnumerable projects) { projects.ToList() - .ForEach(project => _workQueue.AddOrUpdate(project.Id.ToString(), (modified: DateTime.UtcNow, project: project), (_, __) => (modified: DateTime.UtcNow, project: project))); + .ForEach(project => _workQueue.AddOrUpdate(project.Id, (modified: DateTime.UtcNow, project: project), (_, __) => (modified: DateTime.UtcNow, project: project))); } private async Task> Analyze(Project project, CancellationToken token) { - var allAnalyzers = this.providers + var allAnalyzers = this._providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); From 51d0048c3a389527243dd8bd08696552ef82c1fd Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 30 Jul 2018 21:43:14 +0300 Subject: [PATCH 032/178] Testfixes and grouping support on code check service. --- .../Services/Diagnostics/CodeCheckService.cs | 22 ++++++++---- .../Diagnostics/RoslynAnalyzerService.cs | 35 +++++++++++++++---- .../DiagnosticsFacts.cs | 4 +-- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 817a700cd9..9f87ded894 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -36,15 +36,25 @@ public async Task Handle(CodeCheckRequest request) var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult(projects.Select(x => x.Id)); - return new QuickFixResponse(analyzerResults + var locations = analyzerResults .Where(x => (request.FileName == null || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) + .Select(x => new + { + location = x.diagnostic.ToDiagnosticLocation(), + project = x.projectName + }); + + var groupedByProjectWhenMultipleFrameworksAreUsed = locations + .GroupBy(x => x.location) .Select(x => { - var asLocation = x.diagnostic.ToDiagnosticLocation(); - asLocation.Projects = new[] { x.name }; - return asLocation; - }) - .Where(x => x.FileName != null)); + var location = x.First().location; + location.Projects = x.Select(a => a.project).ToList(); + return location; + }); + + return new QuickFixResponse( + groupedByProjectWhenMultipleFrameworksAreUsed.Where(x => x.FileName != null)); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 9750e472ba..514eb29c28 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -60,10 +60,10 @@ private async Task Worker(CancellationToken token) { while (!token.IsCancellationRequested) { + var currentWork = GetThrottledWork(); + try { - var currentWork = GetThrottledWork(); - var analyzerResults = await Task .WhenAll(currentWork .Select(async x => new @@ -82,10 +82,20 @@ private async Task Worker(CancellationToken token) catch (Exception ex) { _logger.LogError($"Analyzer worker failed: {ex}"); + OnErrorInitializeWithEmptyDummyIfNeeded(currentWork); } } } + private void OnErrorInitializeWithEmptyDummyIfNeeded(IDictionary currentWork) + { + currentWork.ToList().ForEach(x => + { + if (!_results.ContainsKey(x.Key)) + _results[x.Key] = ($"errored {x.Key}", Enumerable.Empty()); + }); + } + private IDictionary GetThrottledWork() { lock (_workQueue) @@ -102,23 +112,36 @@ private IDictionary GetThrottledWork() } } - public Task> GetCurrentDiagnosticResult(IEnumerable projectIds) + public Task> GetCurrentDiagnosticResult(IEnumerable projectIds) { return Task.Run(() => { - while (projectIds.Any(projectId => _workQueue.ContainsKey(projectId))) + while(!ResultsInitialized(projectIds) || PendingWork(projectIds)) { Task.Delay(100); } + return _results .Where(x => projectIds.Any(pid => pid == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => (k.Value.name, v)); + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))); }); } + private bool PendingWork(IEnumerable projectIds) + { + return projectIds.Any(x => _workQueue.ContainsKey(x)); + } + + private bool ResultsInitialized(IEnumerable projectIds) + { + return projectIds.All(x => _results.ContainsKey(x)); + } + private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { - if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged) + if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged + || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded + || changeEvent.Kind == WorkspaceChangeKind.ProjectAdded) { var project = changeEvent.NewSolution.GetProject(changeEvent.ProjectId); QueueForAnalysis(new[] { project }); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs index 52213d86bf..d3c50ffe0b 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs @@ -32,13 +32,13 @@ public async Task CodeCheckSpecifiedFileOnly() [Fact] public async Task CheckAllFiles() { + var handler = GetRequestHandler(SharedOmniSharpTestHost); + SharedOmniSharpTestHost.AddFilesToWorkspace( new TestFile("a.cs", "class C1 { int n = true; }"), new TestFile("b.cs", "class C2 { int n = true; }")); - var handler = GetRequestHandler(SharedOmniSharpTestHost); var quickFixes = await handler.Handle(new CodeCheckRequest()); - Assert.Equal(2, quickFixes.QuickFixes.Count()); } } From 3989685f6911264dde3b71615155a192c084168d Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 30 Jul 2018 23:23:02 +0300 Subject: [PATCH 033/178] Testfixes. --- .../Services/Diagnostics/CodeCheckService.cs | 8 ++--- .../RoslynAnalyzerFacts.cs | 29 ++++++------------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 9f87ded894..6a6f5d0761 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -29,15 +29,15 @@ public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService rosl public async Task Handle(CodeCheckRequest request) { - var projects = request.FileName != null - ? _workspace.GetDocuments(request.FileName).Select(x => x.Project) - : _workspace.CurrentSolution.Projects; + var projects = !string.IsNullOrEmpty(request.FileName) + ? _workspace.GetDocuments(request.FileName).Select(x => x.Project).ToList() + : _workspace.CurrentSolution.Projects.ToList(); var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult(projects.Select(x => x.Id)); var locations = analyzerResults - .Where(x => (request.FileName == null || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) + .Where(x => (string.IsNullOrEmpty(request.FileName) || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) .Select(x => new { location = x.diagnostic.ToDiagnosticLocation(), diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 0b98dbb436..f9791b9745 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -92,6 +92,7 @@ public RoslynAnalyzerFacts(ITestOutputHelper output, SharedOmniSharpHostFixture public async Task When_custom_analyzers_are_executed_then_return_results() { var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); @@ -106,15 +107,18 @@ public async Task When_custom_analyzers_are_executed_then_return_results() new[] { testFile }, analyzerRefs: new AnalyzerReference []{ testAnalyzerRef }.ToImmutableArray()); - var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - await AssertForEventuallyMatch( - codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains(analyzerId))); + + + var result = await codeCheckService.Handle(new CodeCheckRequest()); + + Assert.Contains(result.QuickFixes, f => f.Text.Contains(analyzerId)); } [Fact] public async Task Always_return_results_from_net_default_analyzers() { var testFile = new TestFile("testFile.cs", "class SomeClass { int n = true; }"); + var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); @@ -124,25 +128,10 @@ public async Task Always_return_results_from_net_default_analyzers() new[] { "netcoreapp2.1" }, new[] { testFile }); - var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - - await AssertForEventuallyMatch( - codeCheckService.Handle(new CodeCheckRequest()), x => x.QuickFixes.Any(f => f.Text.Contains("CS5001"))); - } - private static async Task AssertForEventuallyMatch(Task func, Predicate check, int retryCount = 10) - { - while (retryCount-- > 0) - { - var result = await func; - - if (check(result)) - return result; - - await Task.Delay(1000); - } + var result = await codeCheckService.Handle(new CodeCheckRequest()); - throw new InvalidOperationException("Timeout expired before meaningfull result returned."); + Assert.Contains(result.QuickFixes, f => f.Text.Contains("CS0029")); } } } From 498645647658de15dac0e6fa014fc7b06eac662d Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 31 Jul 2018 08:38:08 +0300 Subject: [PATCH 034/178] Small naming fix. --- src/OmniSharp.MSBuild/ProjectManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 0919797120..a0168f8a52 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -46,7 +46,7 @@ public ProjectToUpdate(string filePath, bool allowAutoRestore) private readonly HashSet _failedToLoadProjectFiles; private readonly ProjectLoader _projectLoader; private readonly OmniSharpWorkspace _workspace; - private readonly CodeFixesForProjects codeFixesForProject; + private readonly CodeFixesForProjects _codeFixesForProject; private const int LoopDelay = 100; // milliseconds private readonly BufferBlock _queue; private readonly CancellationTokenSource _processLoopCancellation; @@ -66,7 +66,7 @@ public ProjectManager(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, _failedToLoadProjectFiles = new HashSet(StringComparer.OrdinalIgnoreCase); _projectLoader = projectLoader; _workspace = workspace; - this.codeFixesForProject = codeFixesForProject; + _codeFixesForProject = codeFixesForProject; _queue = new BufferBlock(); _processLoopCancellation = new CancellationTokenSource(); _processLoopTask = Task.Run(() => ProcessLoopAsync(_processLoopCancellation.Token)); @@ -266,7 +266,7 @@ private void AddProject(ProjectFileInfo projectFileInfo) var projectInfo = projectFileInfo.CreateProjectInfo(); - codeFixesForProject.LoadFrom(projectInfo.Id.ToString(), projectFileInfo.Analyzers); + _codeFixesForProject.LoadFrom(projectInfo.Id.ToString(), projectFileInfo.Analyzers); var newSolution = _workspace.CurrentSolution.AddProject(projectInfo); From 85116118640ae308ad30a6d9d50ab446fb78f235 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 31 Jul 2018 11:07:46 +0300 Subject: [PATCH 035/178] Implemented first version of project rulesets. --- .../ProjectFileInfo.ProjectData.cs | 29 ++++++++-- .../ProjectFile/ProjectFileInfo.cs | 1 + src/OmniSharp.MSBuild/ProjectManager.cs | 19 +++++- src/OmniSharp.MSBuild/ProjectSystem.cs | 12 ++-- .../Diagnostics/RulesetsForProjects.cs | 58 +++++++++++++++++++ ...ixReference.cs => CodeFixesForProjects.cs} | 0 .../Diagnostics/RoslynAnalyzerService.cs | 8 ++- 7 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs rename src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/{CodeFixReference.cs => CodeFixesForProjects.cs} (100%) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs index 677adbcd03..92dff7d56f 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; +using System.Linq; using System.Runtime.Versioning; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -42,6 +43,7 @@ private class ProjectData public ImmutableArray References { get; } public ImmutableArray PackageReferences { get; } public ImmutableArray Analyzers { get; } + public RuleSet RuleSet { get; } private ProjectData() { @@ -69,7 +71,8 @@ private ProjectData( ImmutableArray preprocessorSymbolNames, ImmutableArray suppressedDiagnosticIds, bool signAssembly, - string assemblyOriginatorKeyFile) + string assemblyOriginatorKeyFile, + RuleSet ruleset) : this() { Guid = guid; @@ -92,6 +95,7 @@ private ProjectData( SignAssembly = signAssembly; AssemblyOriginatorKeyFile = assemblyOriginatorKeyFile; + RuleSet = ruleset; } private ProjectData( @@ -111,10 +115,11 @@ private ProjectData( ImmutableArray projectReferences, ImmutableArray references, ImmutableArray packageReferences, - ImmutableArray analyzers) + ImmutableArray analyzers, + RuleSet ruleset) : this(guid, name, assemblyName, targetPath, outputPath, projectAssetsFile, targetFramework, targetFrameworks, outputKind, languageVersion, allowUnsafeCode, - documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile) + documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, ruleset) { SourceFiles = sourceFiles.EmptyIfDefault(); ProjectReferences = projectReferences.EmptyIfDefault(); @@ -154,7 +159,7 @@ public static ProjectData Create(MSB.Evaluation.Project project) return new ProjectData( guid, name, assemblyName, targetPath, outputPath, projectAssetsFile, targetFramework, targetFrameworks, outputKind, languageVersion, allowUnsafeCode, - documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile); + documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, ruleset: null); } public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance) @@ -185,6 +190,8 @@ public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance) var signAssembly = PropertyConverter.ToBoolean(projectInstance.GetPropertyValue(PropertyNames.SignAssembly), defaultValue: false); var assemblyOriginatorKeyFile = projectInstance.GetPropertyValue(PropertyNames.AssemblyOriginatorKeyFile); + var ruleset = ResolveRulesetIfAny(projectInstance); + var sourceFiles = GetFullPaths( projectInstance.GetItems(ItemNames.Compile), filter: FileNameIsNotGenerated); @@ -218,7 +225,7 @@ public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance) references.Add(fullPath); } } - + var packageReferences = GetPackageReferences(projectInstance.GetItems(ItemNames.PackageReference)); var analyzers = GetFullPaths(projectInstance.GetItems(ItemNames.Analyzer)); @@ -227,7 +234,17 @@ public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance) targetFramework, targetFrameworks, outputKind, languageVersion, allowUnsafeCode, documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, - sourceFiles, projectReferences, references.ToImmutable(), packageReferences, analyzers); + sourceFiles, projectReferences, references.ToImmutable(), packageReferences, analyzers, ruleset); + } + + private static RuleSet ResolveRulesetIfAny(MSB.Execution.ProjectInstance projectInstance) + { + var rulesetIfAny = projectInstance.Properties.SingleOrDefault(x => x.Name == "ResolvedCodeAnalysisRuleSet"); + + if (rulesetIfAny != null) + return RuleSet.LoadEffectiveRuleSetFromFile(Path.Combine(projectInstance.Directory, rulesetIfAny.EvaluatedValue)); + + return null; } private static bool IsCSharpProject(string filePath) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs index b517e36d05..0cba243915 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.cs @@ -38,6 +38,7 @@ internal partial class ProjectFileInfo public bool SignAssembly => _data.SignAssembly; public string AssemblyOriginatorKeyFile => _data.AssemblyOriginatorKeyFile; + public RuleSet RuleSet => _data.RuleSet; public ImmutableArray SourceFiles => _data.SourceFiles; public ImmutableArray References => _data.References; diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index a0168f8a52..9701e6212a 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -16,6 +16,7 @@ using OmniSharp.MSBuild.Logging; using OmniSharp.MSBuild.Models.Events; using OmniSharp.MSBuild.ProjectFile; +using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Roslyn.CSharp.Services.Refactoring.V2; using OmniSharp.Roslyn.Utilities; using OmniSharp.Services; @@ -54,8 +55,18 @@ public ProjectToUpdate(string filePath, bool allowAutoRestore) private bool _processingQueue; private readonly FileSystemNotificationCallback _onDirectoryFileChanged; - - public ProjectManager(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, IFileSystemWatcher fileSystemWatcher, MetadataFileReferenceCache metadataFileReferenceCache, PackageDependencyChecker packageDependencyChecker, ProjectLoader projectLoader, OmniSharpWorkspace workspace, CodeFixesForProjects codeFixesForProject) + private readonly RulesetsForProjects _rulesetsForProjects; + + public ProjectManager( + ILoggerFactory loggerFactory, + IEventEmitter eventEmitter, + IFileSystemWatcher fileSystemWatcher, + MetadataFileReferenceCache metadataFileReferenceCache, + PackageDependencyChecker packageDependencyChecker, + ProjectLoader projectLoader, + OmniSharpWorkspace workspace, + CodeFixesForProjects codeFixesForProject, + RulesetsForProjects rulesetsForProjects) { _logger = loggerFactory.CreateLogger(); _eventEmitter = eventEmitter; @@ -72,6 +83,7 @@ public ProjectManager(ILoggerFactory loggerFactory, IEventEmitter eventEmitter, _processLoopTask = Task.Run(() => ProcessLoopAsync(_processLoopCancellation.Token)); _onDirectoryFileChanged = OnDirectoryFileChanged; + _rulesetsForProjects = rulesetsForProjects; } protected override void DisposeCore(bool disposing) @@ -268,6 +280,9 @@ private void AddProject(ProjectFileInfo projectFileInfo) _codeFixesForProject.LoadFrom(projectInfo.Id.ToString(), projectFileInfo.Analyzers); + if(projectFileInfo.RuleSet != null) + _rulesetsForProjects.AddOrUpdateRuleset(projectFileInfo.Id, projectFileInfo.RuleSet); + var newSolution = _workspace.CurrentSolution.AddProject(projectInfo); if (!_workspace.TryApplyChanges(newSolution)) diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index ec32635943..4f2e57cdde 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -16,6 +16,7 @@ using OmniSharp.MSBuild.ProjectFile; using OmniSharp.MSBuild.SolutionParsing; using OmniSharp.Options; +using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Roslyn.CSharp.Services.Refactoring.V2; using OmniSharp.Services; @@ -34,7 +35,8 @@ public class ProjectSystem : IProjectSystem private readonly IFileSystemWatcher _fileSystemWatcher; private readonly FileSystemHelper _fileSystemHelper; private readonly ILoggerFactory _loggerFactory; - private readonly CodeFixesForProjects codeFixesForProjects; + private readonly CodeFixesForProjects _codeFixesForProjects; + private readonly RulesetsForProjects _rulesetsForProjects; private readonly ILogger _logger; private readonly object _gate = new object(); @@ -63,7 +65,8 @@ public ProjectSystem( IFileSystemWatcher fileSystemWatcher, FileSystemHelper fileSystemHelper, ILoggerFactory loggerFactory, - CodeFixesForProjects codeFixesForProjects) + CodeFixesForProjects codeFixesForProjects, + RulesetsForProjects rulesetsForProjects) { _environment = environment; _workspace = workspace; @@ -75,7 +78,8 @@ public ProjectSystem( _fileSystemWatcher = fileSystemWatcher; _fileSystemHelper = fileSystemHelper; _loggerFactory = loggerFactory; - this.codeFixesForProjects = codeFixesForProjects; + _codeFixesForProjects = codeFixesForProjects; + _rulesetsForProjects = rulesetsForProjects; _projectsToProcess = new Queue(); _logger = loggerFactory.CreateLogger(); } @@ -96,7 +100,7 @@ public void Initalize(IConfiguration configuration) _packageDependencyChecker = new PackageDependencyChecker(_loggerFactory, _eventEmitter, _dotNetCli, _options); _loader = new ProjectLoader(_options, _environment.TargetDirectory, _propertyOverrides, _loggerFactory, _sdksPathResolver); - _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace, codeFixesForProjects); + _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace, _codeFixesForProjects, _rulesetsForProjects); var initialProjectPaths = GetInitialProjectPaths(); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs new file mode 100644 index 0000000000..a28c08e5bb --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs @@ -0,0 +1,58 @@ +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using Microsoft.CodeAnalysis; +using OmniSharp.Roslyn.CSharp.Services.Refactoring.V2; + +namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics +{ + [Shared] + [Export(typeof(RulesetsForProjects))] + public class RulesetsForProjects + { + private readonly ConcurrentDictionary _rules = new ConcurrentDictionary(); + + public void AddOrUpdateRuleset(ProjectId projectId, RuleSet ruleset) + { + _rules.AddOrUpdate(projectId, ruleset, (_,__) => ruleset); + } + + public IEnumerable ApplyRules(ProjectId projectId, IEnumerable originalDiagnostics) + { + var updated = originalDiagnostics + .Select(item => + { + if (_rules.ContainsKey(projectId) && _rules[projectId].SpecificDiagnosticOptions.Any(x => x.Key == item.Id)) + { + var newSeverity = _rules[projectId].SpecificDiagnosticOptions.Single(x => x.Key == item.Id).Value; + + return Diagnostic.Create( + item.Descriptor, + item.Location, + ConvertReportSeverity(newSeverity), + item.AdditionalLocations, + item.Properties, + new object[] {}); + } + return item; + }); + + return updated; + } + + private static DiagnosticSeverity ConvertReportSeverity(ReportDiagnostic reportDiagnostic) + { + switch(reportDiagnostic) { + case ReportDiagnostic.Error: + return DiagnosticSeverity.Error; + case ReportDiagnostic.Warn: + return DiagnosticSeverity.Warning; + case ReportDiagnostic.Info: + return DiagnosticSeverity.Info; + default: + return DiagnosticSeverity.Hidden; + } + } + } +} diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixReference.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs similarity index 100% rename from src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixReference.cs rename to src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 514eb29c28..c90c8f4679 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -30,13 +30,15 @@ public class RoslynAnalyzerService private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; + private readonly RulesetsForProjects _rulesetsForProjects; [ImportingConstructor] public RoslynAnalyzerService( OmniSharpWorkspace workspace, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - DiagnosticEventForwarder forwarder) + DiagnosticEventForwarder forwarder, + RulesetsForProjects rulesetsForProjects) { _logger = loggerFactory.CreateLogger(); _providers = providers; @@ -54,6 +56,7 @@ public RoslynAnalyzerService( _forwarder = forwarder; _workspace = workspace; + _rulesetsForProjects = rulesetsForProjects; } private async Task Worker(CancellationToken token) @@ -75,7 +78,8 @@ private async Task Worker(CancellationToken token) analyzerResults .ToList() - .ForEach(result => _results[result.ProjectId] = (result.ProjectName, result.Result)); + .ForEach(result => _results[result.ProjectId] = + (result.ProjectName, _rulesetsForProjects.ApplyRules(result.ProjectId, result.Result))); await Task.Delay(200, token); } From a62fdc0c81a347e56fa47f48363850d452a1aab6 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 31 Jul 2018 15:40:24 +0300 Subject: [PATCH 036/178] Implemented test for testrules. --- .../RoslynAnalyzerFacts.cs | 40 ++++++++++++++++++- tests/TestUtility/TestHelpers.cs | 7 +++- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index f9791b9745..9a9a58af1c 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using OmniSharp.Models.CodeCheck; +using OmniSharp.Models.Diagnostics; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using TestUtility; using Xunit; @@ -107,8 +109,6 @@ public async Task When_custom_analyzers_are_executed_then_return_results() new[] { testFile }, analyzerRefs: new AnalyzerReference []{ testAnalyzerRef }.ToImmutableArray()); - - var result = await codeCheckService.Handle(new CodeCheckRequest()); Assert.Contains(result.QuickFixes, f => f.Text.Contains(analyzerId)); @@ -133,5 +133,41 @@ public async Task Always_return_results_from_net_default_analyzers() Assert.Contains(result.QuickFixes, f => f.Text.Contains("CS0029")); } + + [Fact] + public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_severity() + { + var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); + var ruleService = SharedOmniSharpTestHost.GetExport(); + + const string analyzerId = "TS1100"; + + var testAnalyzerRef = new TestAnalyzerReference(analyzerId); + + var projectIds = TestHelpers.AddProjectToWorkspace( + SharedOmniSharpTestHost.Workspace, + "project.csproj", + new[] { "netcoreapp2.1" }, + new[] { testFile }, + analyzerRefs: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray()); + + var testRules = new Dictionary + { + { analyzerId, ReportDiagnostic.Hidden } + }; + + ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( + "", + new ReportDiagnostic(), + testRules.ToImmutableDictionary(), + new ImmutableArray())); + + SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); + + var result = await codeCheckService.Handle(new CodeCheckRequest()); + + Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(analyzerId) && f.LogLevel == "Hidden"); + } } } diff --git a/tests/TestUtility/TestHelpers.cs b/tests/TestUtility/TestHelpers.cs index 2478fb0e47..92b01874bb 100644 --- a/tests/TestUtility/TestHelpers.cs +++ b/tests/TestUtility/TestHelpers.cs @@ -38,11 +38,12 @@ public static void AddCsxProjectToWorkspace(OmniSharpWorkspace workspace, TestFi workspace.AddDocument(documentInfo); } - public static void AddProjectToWorkspace(OmniSharpWorkspace workspace, string filePath, string[] frameworks, TestFile[] testFiles, ImmutableArray analyzerRefs = default) + public static IEnumerable AddProjectToWorkspace(OmniSharpWorkspace workspace, string filePath, string[] frameworks, TestFile[] testFiles, ImmutableArray analyzerRefs = default) { var versionStamp = VersionStamp.Create(); var references = GetReferences(); frameworks = frameworks ?? new[] { string.Empty }; + var projectsIds = new List(); foreach (var framework in frameworks) { @@ -69,7 +70,11 @@ public static void AddProjectToWorkspace(OmniSharpWorkspace workspace, string fi workspace.AddDocument(documentInfo); } + + projectsIds.Add(projectInfo.Id); } + + return projectsIds; } private static IEnumerable GetReferences() From 786cbab199d198edd4fc34af85f1f6393cf86550 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 31 Jul 2018 17:43:34 +0300 Subject: [PATCH 037/178] Implemented another test for rulesets. --- .../v1/Diagnostics/DiagnosticLocation.cs | 22 +++++++++- .../Diagnostics/RulesetsForProjects.cs | 35 ++++++++++++---- .../RoslynAnalyzerFacts.cs | 41 ++++++++++++++++--- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/OmniSharp.Abstractions/Models/v1/Diagnostics/DiagnosticLocation.cs b/src/OmniSharp.Abstractions/Models/v1/Diagnostics/DiagnosticLocation.cs index ebe5574921..8ed274166b 100644 --- a/src/OmniSharp.Abstractions/Models/v1/Diagnostics/DiagnosticLocation.cs +++ b/src/OmniSharp.Abstractions/Models/v1/Diagnostics/DiagnosticLocation.cs @@ -1,8 +1,28 @@ -namespace OmniSharp.Models.Diagnostics +using System.Collections.Generic; + +namespace OmniSharp.Models.Diagnostics { public class DiagnosticLocation : QuickFix { public string LogLevel { get; set; } public string Id { get; set; } + + public override bool Equals(object obj) + { + var location = obj as DiagnosticLocation; + return location != null && + base.Equals(obj) && + LogLevel == location.LogLevel && + Id == location.Id; + } + + public override int GetHashCode() + { + var hashCode = -1670479257; + hashCode = hashCode * -1521134295 + base.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(LogLevel); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Id); + return hashCode; + } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs index a28c08e5bb..b956c827dd 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs @@ -23,25 +23,44 @@ public IEnumerable ApplyRules(ProjectId projectId, IEnumerable { - if (_rules.ContainsKey(projectId) && _rules[projectId].SpecificDiagnosticOptions.Any(x => x.Key == item.Id)) + if (IsMatchingDiagnosticRule(projectId, item)) { - var newSeverity = _rules[projectId].SpecificDiagnosticOptions.Single(x => x.Key == item.Id).Value; + var newSeverity = GetNewSeverity(projectId, item); + + if (newSeverity.suppressed) + return null; return Diagnostic.Create( item.Descriptor, item.Location, - ConvertReportSeverity(newSeverity), + newSeverity.severity, item.AdditionalLocations, item.Properties, - new object[] {}); + new object[] { }); } return item; - }); + }) + // Filter out suppressed diagnostics. + .Where(x => x != null) + .ToList(); return updated; } - private static DiagnosticSeverity ConvertReportSeverity(ReportDiagnostic reportDiagnostic) + private (DiagnosticSeverity severity, bool suppressed) GetNewSeverity(ProjectId projectId, Diagnostic item) + { + var rule = _rules[projectId].SpecificDiagnosticOptions.Single(x => x.Key == item.Id).Value; + return ( + severity: ConvertReportSeverity(_rules[projectId].SpecificDiagnosticOptions.Single(x => x.Key == item.Id).Value, item.Severity), + suppressed: rule == ReportDiagnostic.Suppress); + } + + private bool IsMatchingDiagnosticRule(ProjectId projectId, Diagnostic item) + { + return _rules.ContainsKey(projectId) && _rules[projectId].SpecificDiagnosticOptions.Any(x => x.Key == item.Id); + } + + private static DiagnosticSeverity ConvertReportSeverity(ReportDiagnostic reportDiagnostic, DiagnosticSeverity original) { switch(reportDiagnostic) { case ReportDiagnostic.Error: @@ -50,8 +69,10 @@ private static DiagnosticSeverity ConvertReportSeverity(ReportDiagnostic reportD return DiagnosticSeverity.Warning; case ReportDiagnostic.Info: return DiagnosticSeverity.Info; - default: + case ReportDiagnostic.Hidden: return DiagnosticSeverity.Hidden; + default: + return original; } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 9a9a58af1c..60496665c4 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -120,15 +120,12 @@ public async Task Always_return_results_from_net_default_analyzers() var testFile = new TestFile("testFile.cs", "class SomeClass { int n = true; }"); var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - TestHelpers.AddProjectToWorkspace( SharedOmniSharpTestHost.Workspace, "project.csproj", new[] { "netcoreapp2.1" }, new[] { testFile }); - var result = await codeCheckService.Handle(new CodeCheckRequest()); Assert.Contains(result.QuickFixes, f => f.Text.Contains("CS0029")); @@ -163,11 +160,45 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ testRules.ToImmutableDictionary(), new ImmutableArray())); - SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - var result = await codeCheckService.Handle(new CodeCheckRequest()); Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(analyzerId) && f.LogLevel == "Hidden"); } + + [Fact] + // This is important because hidden still allows code fixes to execute, not prevents it, for this reason suppressed analytics should not be returned at all. + public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() + { + var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); + var ruleService = SharedOmniSharpTestHost.GetExport(); + + const string analyzerId = "TS1101"; + + var testAnalyzerRef = new TestAnalyzerReference(analyzerId); + + var projectIds = TestHelpers.AddProjectToWorkspace( + SharedOmniSharpTestHost.Workspace, + "project.csproj", + new[] { "netcoreapp2.1" }, + new[] { testFile }, + analyzerRefs: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray()); + + var testRules = new Dictionary + { + { analyzerId, ReportDiagnostic.Suppress } + }; + + ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( + "", + new ReportDiagnostic(), + testRules.ToImmutableDictionary(), + new ImmutableArray())); + + var result = await codeCheckService.Handle(new CodeCheckRequest()); + + Assert.DoesNotContain(result.QuickFixes.OfType(), f => f.Text.Contains(analyzerId)); + } + } } From bc6c9ef84d64d026c96576c01c413bc84314fb47 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 31 Jul 2018 18:41:28 +0300 Subject: [PATCH 038/178] Tweaks for analyzer responsivity. --- .../Workers/Diagnostics/RoslynAnalyzerService.cs | 7 ++----- tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs | 7 ++++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index c90c8f4679..ee78bdab2f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -26,8 +26,6 @@ public class RoslynAnalyzerService private readonly IEnumerable _providers; - private readonly int throttlingMs = 500; - private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; @@ -81,7 +79,7 @@ private async Task Worker(CancellationToken token) .ForEach(result => _results[result.ProjectId] = (result.ProjectName, _rulesetsForProjects.ApplyRules(result.ProjectId, result.Result))); - await Task.Delay(200, token); + await Task.Delay(100, token); } catch (Exception ex) { @@ -105,9 +103,8 @@ private IDictionary GetThrottledWork() lock (_workQueue) { var currentWork = _workQueue - .Where(x => x.Value.modified.AddMilliseconds(this.throttlingMs) < DateTime.UtcNow) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. - .Take(3) // Limit mount of work executed by once. This is needed on large solution... + .Take(2) // Limit mount of work executed by once. This is needed on large solution... .ToList(); currentWork.Select(x => x.Key).ToList().ForEach(key => _workQueue.TryRemove(key, out _)); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 60496665c4..a6a92b51cc 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -45,7 +45,7 @@ public override ImmutableArray GetAnalyzersForAllLanguages() [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TestDiagnosticAnalyzer : DiagnosticAnalyzer { - public TestDiagnosticAnalyzer(string id) + public TestDiagnosticAnalyzer(string id, bool suppressed = false) { this.id = id; } @@ -200,5 +200,10 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() Assert.DoesNotContain(result.QuickFixes.OfType(), f => f.Text.Contains(analyzerId)); } + [Fact] + public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enable_it() + { + // TODO... + } } } From 1297a62113374b221c4765b898e1420519e63c12 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 31 Jul 2018 19:08:34 +0300 Subject: [PATCH 039/178] Ooppss... --- tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index a6a92b51cc..7c7fe96d7c 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -203,6 +203,7 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() [Fact] public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enable_it() { + await Task.Delay(1); // TODO... } } From 44f79aba6ba19b3567ffdc3323d95ec8e59ac8f3 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 1 Aug 2018 09:34:54 +0300 Subject: [PATCH 040/178] Improved analysis threading. --- .../Diagnostics/RulesetsForProjects.cs | 17 +++ .../Diagnostics/RoslynAnalyzerService.cs | 108 ++++++++---------- 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs index b956c827dd..b4190f1c25 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Linq; using Microsoft.CodeAnalysis; @@ -12,6 +13,22 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class RulesetsForProjects { private readonly ConcurrentDictionary _rules = new ConcurrentDictionary(); + public ImmutableDictionary GetRules(ProjectId projectId) + { + if (!_rules.ContainsKey(projectId)) + return ImmutableDictionary.Empty; + + return _rules[projectId].SpecificDiagnosticOptions; + } + + public CompilationOptions BuildCompilationOptionsWithCurrentRules(Project project) + { + if (!_rules.ContainsKey(project.Id)) + return project.CompilationOptions; + + var existingRules = project.CompilationOptions.SpecificDiagnosticOptions; + return project.CompilationOptions.WithSpecificDiagnosticOptions(existingRules.Concat(GetRules(project.Id))); + } public void AddOrUpdateRuleset(ProjectId projectId, RuleSet ruleset) { diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index ee78bdab2f..1384ef6c56 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -19,7 +19,8 @@ public class RoslynAnalyzerService { private readonly ILogger _logger; - private readonly ConcurrentDictionary _workQueue = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); private readonly ConcurrentDictionary diagnostics)> _results = new ConcurrentDictionary diagnostics)>(); @@ -29,6 +30,7 @@ public class RoslynAnalyzerService private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; + private bool _initializationQueueDone; [ImportingConstructor] public RoslynAnalyzerService( @@ -49,6 +51,7 @@ public RoslynAnalyzerService( { while (!workspace.Initialized) Task.Delay(500); QueueForAnalysis(workspace.CurrentSolution.Projects.ToList()); + _initializationQueueDone = true; _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); }); @@ -61,44 +64,20 @@ private async Task Worker(CancellationToken token) { while (!token.IsCancellationRequested) { - var currentWork = GetThrottledWork(); - try { - var analyzerResults = await Task - .WhenAll(currentWork - .Select(async x => new - { - ProjectId = x.Value.Id, - ProjectName = x.Value.Name, - Result = await Analyze(x.Value, token) - })); - - analyzerResults - .ToList() - .ForEach(result => _results[result.ProjectId] = - (result.ProjectName, _rulesetsForProjects.ApplyRules(result.ProjectId, result.Result))); - + var currentWork = GetThrottledWork(); + await Task.WhenAll(currentWork.Select(x => Analyze(x.project, x.workReadySource, token)).ToList()); await Task.Delay(100, token); } catch (Exception ex) { _logger.LogError($"Analyzer worker failed: {ex}"); - OnErrorInitializeWithEmptyDummyIfNeeded(currentWork); } } } - private void OnErrorInitializeWithEmptyDummyIfNeeded(IDictionary currentWork) - { - currentWork.ToList().ForEach(x => - { - if (!_results.ContainsKey(x.Key)) - _results[x.Key] = ($"errored {x.Key}", Enumerable.Empty()); - }); - } - - private IDictionary GetThrottledWork() + private IEnumerable<(Project project, CancellationTokenSource workReadySource)> GetThrottledWork() { lock (_workQueue) { @@ -109,33 +88,33 @@ private IDictionary GetThrottledWork() currentWork.Select(x => x.Key).ToList().ForEach(key => _workQueue.TryRemove(key, out _)); - return currentWork.ToDictionary(x => x.Key, x => x.Value.project); + return currentWork.Select(x => (x.Value.project, x.Value.workReadySource)); } } - public Task> GetCurrentDiagnosticResult(IEnumerable projectIds) + public async Task> GetCurrentDiagnosticResult(IEnumerable projectIds) { - return Task.Run(() => - { - while(!ResultsInitialized(projectIds) || PendingWork(projectIds)) - { - Task.Delay(100); - } - - return _results - .Where(x => projectIds.Any(pid => pid == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))); - }); - } - - private bool PendingWork(IEnumerable projectIds) - { - return projectIds.Any(x => _workQueue.ContainsKey(x)); + while(!_initializationQueueDone) await Task.Delay(100); + + var pendingWork = _workQueue + .Where(x => projectIds.Any(pid => pid == x.Key)) + .Select(x => { + _logger.LogInformation($"Starting to wait: {x.Key}"); + return x; + }) + .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) + .ContinueWith(task => LogTimeouts(task, x.Key))); + + await Task.WhenAll(pendingWork); + + return _results + .Where(x => projectIds.Any(pid => pid == x.Key)) + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))); } - private bool ResultsInitialized(IEnumerable projectIds) + private void LogTimeouts(Task task, ProjectId projectId) { - return projectIds.All(x => _results.ContainsKey(x)); + if(!task.IsCanceled) _logger.LogError($"Timeout before work got ready for project {projectId}."); } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) @@ -152,23 +131,34 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv private void QueueForAnalysis(IEnumerable projects) { projects.ToList() - .ForEach(project => _workQueue.AddOrUpdate(project.Id, (modified: DateTime.UtcNow, project: project), (_, __) => (modified: DateTime.UtcNow, project: project))); + .ForEach(project => _workQueue.AddOrUpdate(project.Id, + (modified: DateTime.UtcNow, project: project, new CancellationTokenSource()), + (_, oldValue) => (modified: DateTime.UtcNow, project: project, oldValue.workReadySource))); } - private async Task> Analyze(Project project, CancellationToken token) + private async Task Analyze(Project project, CancellationTokenSource workReadySource, CancellationToken token) { - var allAnalyzers = this._providers - .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) - .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); + try + { + var allAnalyzers = this._providers + .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) + .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); - if (!allAnalyzers.Any()) - return ImmutableArray.Empty; + var compiled = await project.WithCompilationOptions( + _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) + .GetCompilationAsync(token); - var compiled = await project.GetCompilationAsync(token); + var results = await compiled + .WithAnalyzers(allAnalyzers.ToImmutableArray()) + .GetAllDiagnosticsAsync(token); - return await compiled - .WithAnalyzers(allAnalyzers.ToImmutableArray()) - .GetAllDiagnosticsAsync(token); + _results[project.Id] = (project.Name, results); + } + finally + { + _logger.LogInformation($"DONE: {project.Id}"); + workReadySource.Cancel(); + } } } } From 19ee0ea4bc2e4428407e5f65826975a3619aca38 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 1 Aug 2018 09:52:14 +0300 Subject: [PATCH 041/178] Cleanup. --- .../Workers/Diagnostics/RoslynAnalyzerService.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 1384ef6c56..75d2edefaa 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -31,6 +31,7 @@ public class RoslynAnalyzerService private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; private bool _initializationQueueDone; + private int _throttlingMs = 500; [ImportingConstructor] public RoslynAnalyzerService( @@ -82,6 +83,7 @@ private async Task Worker(CancellationToken token) lock (_workQueue) { var currentWork = _workQueue + .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. .Take(2) // Limit mount of work executed by once. This is needed on large solution... .ToList(); @@ -98,10 +100,6 @@ private async Task Worker(CancellationToken token) var pendingWork = _workQueue .Where(x => projectIds.Any(pid => pid == x.Key)) - .Select(x => { - _logger.LogInformation($"Starting to wait: {x.Key}"); - return x; - }) .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) .ContinueWith(task => LogTimeouts(task, x.Key))); @@ -156,7 +154,6 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou } finally { - _logger.LogInformation($"DONE: {project.Id}"); workReadySource.Cancel(); } } From 9e83c60184285b322e9f4d34fa785d1cb88cbe9b Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 1 Aug 2018 11:50:27 +0300 Subject: [PATCH 042/178] User experience and test tweaks. --- .../Diagnostics/RulesetsForProjects.cs | 59 ------------------- .../Diagnostics/RoslynAnalyzerService.cs | 4 ++ .../RoslynAnalyzerFacts.cs | 8 +-- 3 files changed, 8 insertions(+), 63 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs index b4190f1c25..a6247c6a31 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs @@ -4,7 +4,6 @@ using System.Composition; using System.Linq; using Microsoft.CodeAnalysis; -using OmniSharp.Roslyn.CSharp.Services.Refactoring.V2; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { @@ -34,63 +33,5 @@ public void AddOrUpdateRuleset(ProjectId projectId, RuleSet ruleset) { _rules.AddOrUpdate(projectId, ruleset, (_,__) => ruleset); } - - public IEnumerable ApplyRules(ProjectId projectId, IEnumerable originalDiagnostics) - { - var updated = originalDiagnostics - .Select(item => - { - if (IsMatchingDiagnosticRule(projectId, item)) - { - var newSeverity = GetNewSeverity(projectId, item); - - if (newSeverity.suppressed) - return null; - - return Diagnostic.Create( - item.Descriptor, - item.Location, - newSeverity.severity, - item.AdditionalLocations, - item.Properties, - new object[] { }); - } - return item; - }) - // Filter out suppressed diagnostics. - .Where(x => x != null) - .ToList(); - - return updated; - } - - private (DiagnosticSeverity severity, bool suppressed) GetNewSeverity(ProjectId projectId, Diagnostic item) - { - var rule = _rules[projectId].SpecificDiagnosticOptions.Single(x => x.Key == item.Id).Value; - return ( - severity: ConvertReportSeverity(_rules[projectId].SpecificDiagnosticOptions.Single(x => x.Key == item.Id).Value, item.Severity), - suppressed: rule == ReportDiagnostic.Suppress); - } - - private bool IsMatchingDiagnosticRule(ProjectId projectId, Diagnostic item) - { - return _rules.ContainsKey(projectId) && _rules[projectId].SpecificDiagnosticOptions.Any(x => x.Key == item.Id); - } - - private static DiagnosticSeverity ConvertReportSeverity(ReportDiagnostic reportDiagnostic, DiagnosticSeverity original) - { - switch(reportDiagnostic) { - case ReportDiagnostic.Error: - return DiagnosticSeverity.Error; - case ReportDiagnostic.Warn: - return DiagnosticSeverity.Warning; - case ReportDiagnostic.Info: - return DiagnosticSeverity.Info; - case ReportDiagnostic.Hidden: - return DiagnosticSeverity.Hidden; - default: - return original; - } - } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 75d2edefaa..54d8b5be44 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -98,6 +98,10 @@ private async Task Worker(CancellationToken token) { while(!_initializationQueueDone) await Task.Delay(100); + // Since results are queried, it's better to return something even if that means litle wait. (this is hack...) + if (_workQueue.Count(x => projectIds.Any(pid => pid == x.Key)) == 0) + await Task.Delay(250); + var pendingWork = _workQueue .Where(x => projectIds.Any(pid => pid == x.Key)) .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 7c7fe96d7c..1dd64307ec 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -117,7 +117,7 @@ public async Task When_custom_analyzers_are_executed_then_return_results() [Fact] public async Task Always_return_results_from_net_default_analyzers() { - var testFile = new TestFile("testFile.cs", "class SomeClass { int n = true; }"); + var testFile = new TestFile("testFile_1.cs", "class SomeClass { int n = true; }"); var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); TestHelpers.AddProjectToWorkspace( @@ -128,13 +128,13 @@ public async Task Always_return_results_from_net_default_analyzers() var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.Contains(result.QuickFixes, f => f.Text.Contains("CS0029")); + Assert.Contains(result.QuickFixes.Where(x => x.FileName == testFile.FileName), f => f.Text.Contains("CS")); } [Fact] public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_severity() { - var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var testFile = new TestFile("testFile_2.cs", "class _this_is_invalid_test_class_name { int n = true; }"); var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); var ruleService = SharedOmniSharpTestHost.GetExport(); @@ -169,7 +169,7 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ // This is important because hidden still allows code fixes to execute, not prevents it, for this reason suppressed analytics should not be returned at all. public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() { - var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var testFile = new TestFile("testFile_3.cs", "class _this_is_invalid_test_class_name { int n = true; }"); var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); var ruleService = SharedOmniSharpTestHost.GetExport(); From b32b04de0e4854a970c523c537c208741da70b82 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 2 Aug 2018 22:55:09 +0300 Subject: [PATCH 043/178] Implemented final tests for rules. --- omnisharp.json | 5 ++ .../v1/AutoComplete/AutoCompleteResponse.cs | 1 + .../RoslynAnalyzerFacts.cs | 80 ++++++++++++------- 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/omnisharp.json b/omnisharp.json index 541993be8a..35b554b7c3 100644 --- a/omnisharp.json +++ b/omnisharp.json @@ -1,5 +1,10 @@ { "dotnet": { "enabled": false + }, + "RoslynExtensionsOptions": { + "LocationPaths": [ + "C:\\RoslynAnalyzers" + ] } } \ No newline at end of file diff --git a/src/OmniSharp.Abstractions/Models/v1/AutoComplete/AutoCompleteResponse.cs b/src/OmniSharp.Abstractions/Models/v1/AutoComplete/AutoCompleteResponse.cs index 52effc260e..802f52b723 100644 --- a/src/OmniSharp.Abstractions/Models/v1/AutoComplete/AutoCompleteResponse.cs +++ b/src/OmniSharp.Abstractions/Models/v1/AutoComplete/AutoCompleteResponse.cs @@ -8,6 +8,7 @@ public class AutoCompleteResponse /// public string CompletionText { get; set; } public string Description { get; set; } + /// /// The text that should be displayed in the auto-complete UI. /// diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 1dd64307ec..57dc33e470 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -21,10 +21,12 @@ public class RoslynAnalyzerFacts : AbstractSingleRequestHandlerTestFixture null; @@ -33,21 +35,22 @@ public TestAnalyzerReference(string testAnalyzerId) public override ImmutableArray GetAnalyzers(string language) { - return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer(Id.ToString()) }.ToImmutableArray(); + return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer(Id.ToString(), _isEnabledByDefault) }.ToImmutableArray(); } public override ImmutableArray GetAnalyzersForAllLanguages() { - return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer(Id.ToString()) }.ToImmutableArray(); + return new DiagnosticAnalyzer[] { new TestDiagnosticAnalyzer(Id.ToString(), _isEnabledByDefault) }.ToImmutableArray(); } } [DiagnosticAnalyzer(LanguageNames.CSharp)] public class TestDiagnosticAnalyzer : DiagnosticAnalyzer { - public TestDiagnosticAnalyzer(string id, bool suppressed = false) + public TestDiagnosticAnalyzer(string id, bool isEnabledByDefault) { this.id = id; + _isEnabledByDefault = isEnabledByDefault; } private DiagnosticDescriptor Rule => new DiagnosticDescriptor( @@ -56,10 +59,11 @@ public TestDiagnosticAnalyzer(string id, bool suppressed = false) "Type name '{0}' contains lowercase letters", "Naming", DiagnosticSeverity.Warning, - isEnabledByDefault: true + isEnabledByDefault: _isEnabledByDefault ); private readonly string id; + private readonly bool _isEnabledByDefault; public override ImmutableArray SupportedDiagnostics { @@ -102,12 +106,7 @@ public async Task When_custom_analyzers_are_executed_then_return_results() var testAnalyzerRef = new TestAnalyzerReference(analyzerId); - TestHelpers.AddProjectToWorkspace( - SharedOmniSharpTestHost.Workspace, - "project.csproj", - new[] { "netcoreapp2.1" }, - new[] { testFile }, - analyzerRefs: new AnalyzerReference []{ testAnalyzerRef }.ToImmutableArray()); + var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); var result = await codeCheckService.Handle(new CodeCheckRequest()); @@ -120,11 +119,7 @@ public async Task Always_return_results_from_net_default_analyzers() var testFile = new TestFile("testFile_1.cs", "class SomeClass { int n = true; }"); var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - TestHelpers.AddProjectToWorkspace( - SharedOmniSharpTestHost.Workspace, - "project.csproj", - new[] { "netcoreapp2.1" }, - new[] { testFile }); + CreateProjectWitFile(testFile); var result = await codeCheckService.Handle(new CodeCheckRequest()); @@ -142,12 +137,7 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ var testAnalyzerRef = new TestAnalyzerReference(analyzerId); - var projectIds = TestHelpers.AddProjectToWorkspace( - SharedOmniSharpTestHost.Workspace, - "project.csproj", - new[] { "netcoreapp2.1" }, - new[] { testFile }, - analyzerRefs: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray()); + var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); var testRules = new Dictionary { @@ -177,12 +167,7 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() var testAnalyzerRef = new TestAnalyzerReference(analyzerId); - var projectIds = TestHelpers.AddProjectToWorkspace( - SharedOmniSharpTestHost.Workspace, - "project.csproj", - new[] { "netcoreapp2.1" }, - new[] { testFile }, - analyzerRefs: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray()); + var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); var testRules = new Dictionary { @@ -203,8 +188,43 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() [Fact] public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enable_it() { - await Task.Delay(1); - // TODO... + var testFile = new TestFile("testFile_3.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); + var ruleService = SharedOmniSharpTestHost.GetExport(); + + const string analyzerId = "TS1101"; + + var testAnalyzerRef = new TestAnalyzerReference(analyzerId, isEnabledByDefault: false); + + var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); + + var testRules = new Dictionary + { + { analyzerId, ReportDiagnostic.Error } + }; + + ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( + "", + new ReportDiagnostic(), + testRules.ToImmutableDictionary(), + new ImmutableArray())); + + var result = await codeCheckService.Handle(new CodeCheckRequest()); + + Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(analyzerId)); + } + + private IEnumerable CreateProjectWitFile(TestFile testFile, TestAnalyzerReference testAnalyzerRef = null) + { + var analyzerReferences = testAnalyzerRef == null ? new AnalyzerReference[] { }.ToImmutableArray() : + new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray(); + + return TestHelpers.AddProjectToWorkspace( + SharedOmniSharpTestHost.Workspace, + "project.csproj", + new[] { "netcoreapp2.1" }, + new[] { testFile }, + analyzerRefs: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray()); } } } From 4844226cdfca78dd34298fbb9dee4d2ce3a75b0e Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 2 Aug 2018 22:55:48 +0300 Subject: [PATCH 044/178] Undo for accidentally pushed change. --- omnisharp.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/omnisharp.json b/omnisharp.json index 35b554b7c3..541993be8a 100644 --- a/omnisharp.json +++ b/omnisharp.json @@ -1,10 +1,5 @@ { "dotnet": { "enabled": false - }, - "RoslynExtensionsOptions": { - "LocationPaths": [ - "C:\\RoslynAnalyzers" - ] } } \ No newline at end of file From 7ae79b0991a4c0adbfb6339123369babc14533dd Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 2 Aug 2018 23:18:07 +0300 Subject: [PATCH 045/178] Fixed test. --- tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 57dc33e470..964cb2e61b 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -216,7 +216,7 @@ public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enab private IEnumerable CreateProjectWitFile(TestFile testFile, TestAnalyzerReference testAnalyzerRef = null) { - var analyzerReferences = testAnalyzerRef == null ? new AnalyzerReference[] { }.ToImmutableArray() : + var analyzerReferences = testAnalyzerRef == null ? default: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray(); return TestHelpers.AddProjectToWorkspace( @@ -224,7 +224,7 @@ private IEnumerable CreateProjectWitFile(TestFile testFile, TestAnaly "project.csproj", new[] { "netcoreapp2.1" }, new[] { testFile }, - analyzerRefs: new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray()); + analyzerRefs: analyzerReferences); } } } From a06ddb658b0742a0668e07614ed1725ae7beab59 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 5 Aug 2018 20:09:01 +0300 Subject: [PATCH 046/178] Refactored tests. --- .../RoslynAnalyzerFacts.cs | 48 ++++++++----------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs index 964cb2e61b..cea1d4c1e8 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs @@ -102,15 +102,13 @@ public async Task When_custom_analyzers_are_executed_then_return_results() SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - var analyzerId = "TS1000".ToString(); - - var testAnalyzerRef = new TestAnalyzerReference(analyzerId); + var testAnalyzerRef = new TestAnalyzerReference("TS1234"); var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.Contains(result.QuickFixes, f => f.Text.Contains(analyzerId)); + Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } [Fact] @@ -133,16 +131,10 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); var ruleService = SharedOmniSharpTestHost.GetExport(); - const string analyzerId = "TS1100"; - - var testAnalyzerRef = new TestAnalyzerReference(analyzerId); + var testAnalyzerRef = new TestAnalyzerReference("TS1100"); var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); - - var testRules = new Dictionary - { - { analyzerId, ReportDiagnostic.Hidden } - }; + var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Hidden); ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( "", @@ -152,7 +144,15 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(analyzerId) && f.LogLevel == "Hidden"); + Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); + } + + private static Dictionary CreateRules(TestAnalyzerReference testAnalyzerRef, ReportDiagnostic diagnostic) + { + return new Dictionary + { + { testAnalyzerRef.Id.ToString(), diagnostic } + }; } [Fact] @@ -163,16 +163,11 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); var ruleService = SharedOmniSharpTestHost.GetExport(); - const string analyzerId = "TS1101"; - - var testAnalyzerRef = new TestAnalyzerReference(analyzerId); + var testAnalyzerRef = new TestAnalyzerReference("TS1101"); var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); - var testRules = new Dictionary - { - { analyzerId, ReportDiagnostic.Suppress } - }; + var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Suppress); ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( "", @@ -182,7 +177,7 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.DoesNotContain(result.QuickFixes.OfType(), f => f.Text.Contains(analyzerId)); + Assert.DoesNotContain(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } [Fact] @@ -192,16 +187,11 @@ public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enab var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); var ruleService = SharedOmniSharpTestHost.GetExport(); - const string analyzerId = "TS1101"; - - var testAnalyzerRef = new TestAnalyzerReference(analyzerId, isEnabledByDefault: false); + var testAnalyzerRef = new TestAnalyzerReference("TS1101", isEnabledByDefault: false); var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); - var testRules = new Dictionary - { - { analyzerId, ReportDiagnostic.Error } - }; + var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Error); ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( "", @@ -211,7 +201,7 @@ public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enab var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(analyzerId)); + Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } private IEnumerable CreateProjectWitFile(TestFile testFile, TestAnalyzerReference testAnalyzerRef = null) From f10fa7b4c555ddea2c3266fbffc74d6cc02ef862 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 8 Aug 2018 20:12:34 +0300 Subject: [PATCH 047/178] Review fixes expect assembly loader from DI. --- .../Utilities/ReflectionExtensions.cs | 16 +++++++++ src/OmniSharp.Host/Services/AssemblyLoader.cs | 13 ++++++- .../ProjectFile/AnalyzerAssemblyLoader.cs | 26 -------------- .../ProjectFileInfo.ProjectData.cs | 2 +- .../ProjectFile/ProjectFileInfoExtensions.cs | 21 +++++++----- .../Services/Diagnostics/CodeCheckService.cs | 4 +-- .../Refactoring/V2/BaseCodeActionService.cs | 2 -- .../Refactoring/V2/CodeFixesForProjects.cs | 34 ++++++------------- .../Diagnostics/RoslynAnalyzerService.cs | 17 ++++++---- .../Services/AbstractCodeActionProvider.cs | 23 +++---------- .../CustomCodeFixesFacts.cs | 13 +++++++ 11 files changed, 83 insertions(+), 88 deletions(-) delete mode 100644 src/OmniSharp.MSBuild/ProjectFile/AnalyzerAssemblyLoader.cs create mode 100644 tests/OmniSharp.Roslyn.CSharp.Tests/CustomCodeFixesFacts.cs diff --git a/src/OmniSharp.Abstractions/Utilities/ReflectionExtensions.cs b/src/OmniSharp.Abstractions/Utilities/ReflectionExtensions.cs index d79b0d6412..d997734995 100644 --- a/src/OmniSharp.Abstractions/Utilities/ReflectionExtensions.cs +++ b/src/OmniSharp.Abstractions/Utilities/ReflectionExtensions.cs @@ -113,6 +113,22 @@ public static object CreateInstance(this Lazy lazyType, params object[] ar return Activator.CreateInstance(lazyType.Value, args); } + public static T CreateInstance(this Type type) where T : class + { + try + { + var defaultCtor = type.GetConstructor(new Type[] { }); + + return defaultCtor != null + ? (T)Activator.CreateInstance(type) + : null; + } + catch (Exception ex) + { + throw new InvalidOperationException($"Failed to create instrance of {type.FullName} in {type.AssemblyQualifiedName}.", ex); + } + } + public static T Invoke(this MethodInfo methodInfo, object obj, object[] args) { if (methodInfo == null) diff --git a/src/OmniSharp.Host/Services/AssemblyLoader.cs b/src/OmniSharp.Host/Services/AssemblyLoader.cs index 2766d1b4bd..28d24dd627 100644 --- a/src/OmniSharp.Host/Services/AssemblyLoader.cs +++ b/src/OmniSharp.Host/Services/AssemblyLoader.cs @@ -5,10 +5,11 @@ using System.IO; using System.Reflection; using Microsoft.Extensions.Logging; +using Microsoft.CodeAnalysis; namespace OmniSharp.Services { - internal class AssemblyLoader : IAssemblyLoader + internal class AssemblyLoader : IAssemblyLoader, IAnalyzerAssemblyLoader { private static readonly ConcurrentDictionary AssemblyCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ILogger _logger; @@ -18,6 +19,11 @@ public AssemblyLoader(ILoggerFactory loggerFactory) _logger = loggerFactory.CreateLogger(); } + public void AddDependencyLocation(string fullPath) + { + LoadFrom(fullPath); + } + public Assembly Load(AssemblyName name) { Assembly result = null; @@ -90,5 +96,10 @@ public Assembly LoadFrom(string assemblyPath, bool dontLockAssemblyOnDisk = fals _logger.LogTrace($"Assembly loaded from path: {assemblyPath}"); return assembly; } + + public Assembly LoadFromPath(string fullPath) + { + throw new NotImplementedException(); + } } } diff --git a/src/OmniSharp.MSBuild/ProjectFile/AnalyzerAssemblyLoader.cs b/src/OmniSharp.MSBuild/ProjectFile/AnalyzerAssemblyLoader.cs deleted file mode 100644 index 3ed856187e..0000000000 --- a/src/OmniSharp.MSBuild/ProjectFile/AnalyzerAssemblyLoader.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Reflection; -using Microsoft.CodeAnalysis; - -namespace OmniSharp.MSBuild.ProjectFile -{ - public class AnalyzerAssemblyLoader : IAnalyzerAssemblyLoader - { - private readonly ConcurrentDictionary _assemblyPaths = new ConcurrentDictionary(); - - public void AddDependencyLocation(string fullPath) - { - if(!_assemblyPaths.ContainsKey(fullPath)) - _assemblyPaths.TryAdd(fullPath, Assembly.LoadFrom(fullPath)); - } - - public Assembly LoadFromPath(string fullPath) - { - if (!_assemblyPaths.ContainsKey(fullPath)) - throw new InvalidOperationException($"Could not find analyzer reference '{fullPath}' from cache."); - - return _assemblyPaths[fullPath]; - } - } -} diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs index 92dff7d56f..66782154bd 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs @@ -239,7 +239,7 @@ public static ProjectData Create(MSB.Execution.ProjectInstance projectInstance) private static RuleSet ResolveRulesetIfAny(MSB.Execution.ProjectInstance projectInstance) { - var rulesetIfAny = projectInstance.Properties.SingleOrDefault(x => x.Name == "ResolvedCodeAnalysisRuleSet"); + var rulesetIfAny = projectInstance.Properties.FirstOrDefault(x => x.Name == "ResolvedCodeAnalysisRuleSet"); if (rulesetIfAny != null) return RuleSet.LoadEffectiveRuleSetFromFile(Path.Combine(projectInstance.Directory, rulesetIfAny.EvaluatedValue)); diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index dc8104449d..05b9eba131 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using Microsoft.CodeAnalysis; @@ -38,9 +39,9 @@ public static CSharpCompilationOptions CreateCompilationOptions(this ProjectFile return result; } - public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo) + public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo, IAnalyzerAssemblyLoader analyzerAssemblyLoader) { - var analyzerReferences = ResolveAnalyzerReferencesForProject(projectFileInfo); + var analyzerReferences = ResolveAnalyzerReferencesForProject(projectFileInfo, analyzerAssemblyLoader); return ProjectInfo.Create( id: projectFileInfo.Id, @@ -54,7 +55,7 @@ public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo analyzerReferences: analyzerReferences); } - private static IEnumerable ResolveAnalyzerReferencesForProject(ProjectFileInfo projectFileInfo) + private static IEnumerable ResolveAnalyzerReferencesForProject(ProjectFileInfo projectFileInfo, IAnalyzerAssemblyLoader analyzerAssemblyLoader) { return projectFileInfo.Analyzers .GroupBy(x => Path.GetDirectoryName(x)) @@ -62,11 +63,15 @@ private static IEnumerable ResolveAnalyzerReferencesForProjec { // Is there better way to figure out entry assembly for specific nuget analyzer package? And is there even entry assembly or is better way to just lookup all referenced assemblies? return singleAnalyzerPackageGroup - .Where(x => x.EndsWith("Analyzers.dll") || x.EndsWith("Analyzer.dll")) - .Select(analyzerMainAssembly => { - var assemblyLoader = new AnalyzerAssemblyLoader(); - singleAnalyzerPackageGroup.ToList().ForEach(x => assemblyLoader.AddDependencyLocation(x)); - return new AnalyzerFileReference(analyzerMainAssembly, assemblyLoader); + .Where(x => x.EndsWith("analyzers.dll", ignoreCase: true, CultureInfo.InvariantCulture)) + .Select(analyzerMainAssembly => + { + foreach(var assembly in singleAnalyzerPackageGroup) + { + analyzerAssemblyLoader.AddDependencyLocation(assembly); + }; + + return new AnalyzerFileReference(analyzerMainAssembly, analyzerAssemblyLoader); }); }); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 6a6f5d0761..7b150a1d25 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -30,8 +30,8 @@ public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService rosl public async Task Handle(CodeCheckRequest request) { var projects = !string.IsNullOrEmpty(request.FileName) - ? _workspace.GetDocuments(request.FileName).Select(x => x.Project).ToList() - : _workspace.CurrentSolution.Projects.ToList(); + ? _workspace.GetDocuments(request.FileName).Select(x => x.Project) + : _workspace.CurrentSolution.Projects; var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult(projects.Select(x => x.Id)); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 0be47c8256..269307e6d8 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -14,11 +14,9 @@ using OmniSharp.Extensions; using OmniSharp.Mef; using OmniSharp.Models.V2.CodeActions; -using OmniSharp.Roslyn.CSharp.Services.CodeActions; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Services; using OmniSharp.Utilities; -using System.IO; namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs index 9295ae2676..90f6cd9f73 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs @@ -1,5 +1,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; +using OmniSharp.Services; +using OmniSharp.Utilities; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -14,17 +16,19 @@ namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 [Export(typeof(CodeFixesForProjects))] public class CodeFixesForProjects { - private readonly ConcurrentDictionary> codeFixCache = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _codeFixCache = new ConcurrentDictionary>(); + private readonly IAssemblyLoader _assemblyLoader; [ImportingConstructor] - public CodeFixesForProjects() + public CodeFixesForProjects(IAssemblyLoader assemblyLoader) { + _assemblyLoader = assemblyLoader; } public IEnumerable GetAllCodeFixesForProject(string projectId) { - if (codeFixCache.ContainsKey(projectId)) - return codeFixCache[projectId]; + if (_codeFixCache.ContainsKey(projectId)) + return _codeFixCache[projectId]; return Enumerable.Empty(); } @@ -34,33 +38,17 @@ public void LoadFrom(string projectId, IEnumerable AnalyzerPaths) .Where(x => x.EndsWith("CodeFixes.dll")) .SelectMany(codeFixDllPath => { - var loadedAssembly = Assembly.LoadFrom(codeFixDllPath); + var loadedAssembly = _assemblyLoader.LoadFrom(codeFixDllPath); var validTypes = loadedAssembly.GetTypes() .Where(type => !type.GetTypeInfo().IsInterface && !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().ContainsGenericParameters) .Where(t => typeof(CodeFixProvider).IsAssignableFrom(t)); return validTypes - .Select(type => CreateInstance(type)) + .Select(type => type.CreateInstance()) .Where(instance => instance != null); }); - codeFixCache.AddOrUpdate(projectId, codeFixes, (_, __) => codeFixes); - } - - private static T CreateInstance(Type type) where T : class - { - try - { - var defaultCtor = type.GetConstructor(new Type[] { }); - - return defaultCtor != null - ? (T)Activator.CreateInstance(type) - : null; - } - catch (Exception ex) - { - throw new InvalidOperationException($"Failed to create instrance of {type.FullName} in {type.AssemblyQualifiedName}.", ex); - } + _codeFixCache.AddOrUpdate(projectId, codeFixes, (_, __) => codeFixes); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 54d8b5be44..f1af188400 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -51,7 +51,7 @@ public RoslynAnalyzerService( Task.Run(() => { while (!workspace.Initialized) Task.Delay(500); - QueueForAnalysis(workspace.CurrentSolution.Projects.ToList()); + QueueForAnalysis(workspace.CurrentSolution.Projects); _initializationQueueDone = true; _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); }); @@ -68,7 +68,7 @@ private async Task Worker(CancellationToken token) try { var currentWork = GetThrottledWork(); - await Task.WhenAll(currentWork.Select(x => Analyze(x.project, x.workReadySource, token)).ToList()); + await Task.WhenAll(currentWork.Select(x => Analyze(x.project, x.workReadySource, token))); await Task.Delay(100, token); } catch (Exception ex) @@ -88,7 +88,10 @@ private async Task Worker(CancellationToken token) .Take(2) // Limit mount of work executed by once. This is needed on large solution... .ToList(); - currentWork.Select(x => x.Key).ToList().ForEach(key => _workQueue.TryRemove(key, out _)); + foreach(var workKey in currentWork.Select(x => x.Key)) + { + _workQueue.TryRemove(workKey, out _); + } return currentWork.Select(x => (x.Value.project, x.Value.workReadySource)); } @@ -132,10 +135,12 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv private void QueueForAnalysis(IEnumerable projects) { - projects.ToList() - .ForEach(project => _workQueue.AddOrUpdate(project.Id, + foreach(var project in projects) + { + _workQueue.AddOrUpdate(project.Id, (modified: DateTime.UtcNow, project: project, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, project: project, oldValue.workReadySource))); + (_, oldValue) => (modified: DateTime.UtcNow, project: project, oldValue.workReadySource)); + } } private async Task Analyze(Project project, CancellationTokenSource workReadySource, CancellationToken token) diff --git a/src/OmniSharp.Roslyn/Services/AbstractCodeActionProvider.cs b/src/OmniSharp.Roslyn/Services/AbstractCodeActionProvider.cs index 33210321e5..db49e43c41 100644 --- a/src/OmniSharp.Roslyn/Services/AbstractCodeActionProvider.cs +++ b/src/OmniSharp.Roslyn/Services/AbstractCodeActionProvider.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CodeRefactorings; using Microsoft.CodeAnalysis.Diagnostics; +using OmniSharp.Utilities; namespace OmniSharp.Services { @@ -31,38 +32,22 @@ protected AbstractCodeActionProvider(string providerName, ImmutableArray typeof(CodeRefactoringProvider).IsAssignableFrom(t)) - .Select(type => CreateInstance(type)) + .Select(type => type.CreateInstance()) .Where(instance => instance != null) .ToImmutableArray(); this.CodeFixProviders = types .Where(t => typeof(CodeFixProvider).IsAssignableFrom(t)) - .Select(type => CreateInstance(type)) + .Select(type => type.CreateInstance()) .Where(instance => instance != null) .ToImmutableArray(); this.CodeDiagnosticAnalyzerProviders = types .Where(t => typeof(DiagnosticAnalyzer).IsAssignableFrom(t)) - .Select(type => CreateInstance(type)) + .Select(type => type.CreateInstance()) .Where(instance => instance != null) .ToImmutableArray(); } - - private T CreateInstance(Type type) where T : class - { - try - { - var defaultCtor = type.GetConstructor(new Type[] { }); - - return defaultCtor != null - ? (T)Activator.CreateInstance(type) - : null; - } - catch (Exception ex) - { - throw new InvalidOperationException($"Failed to create instrance of {type.FullName} in {type.AssemblyQualifiedName}.", ex); - } - } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomCodeFixesFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomCodeFixesFacts.cs new file mode 100644 index 0000000000..be4718f162 --- /dev/null +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomCodeFixesFacts.cs @@ -0,0 +1,13 @@ +using Xunit; + +namespace OmniSharp.Roslyn.CSharp.Tests +{ + public class CustomCodeFixesFacts + { + [Fact] + public void When_custom_diagnostics_is_executed_then_update_sources() + { + + } + } +} \ No newline at end of file From f4b829cf422a2389e24da09b3afc13ae0c5e24e3 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 8 Aug 2018 20:17:24 +0300 Subject: [PATCH 048/178] Sorted usings. --- .../Services/Diagnostics/CodeCheckService.cs | 4 ++-- .../Services/Refactoring/V2/CodeFixesForProjects.cs | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 7b150a1d25..15903bf415 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -1,14 +1,14 @@ using System.Composition; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; using OmniSharp.Helpers; using OmniSharp.Mef; using OmniSharp.Models; using OmniSharp.Models.CodeCheck; -using System.Collections.Generic; using OmniSharp.Models.Diagnostics; -using Microsoft.Extensions.Logging; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs index 90f6cd9f73..8e9a9832a9 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs @@ -1,14 +1,12 @@ -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using OmniSharp.Services; -using OmniSharp.Utilities; -using System; -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Composition; -using System.IO; using System.Linq; using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using OmniSharp.Services; +using OmniSharp.Utilities; namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 { From 00da2a93b89773df3a12a26b3c7361d78c1c0ec5 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 8 Aug 2018 20:38:59 +0300 Subject: [PATCH 049/178] DI fixes, theres currently issue during runtime however. --- src/OmniSharp.Host/CompositionHostBuilder.cs | 4 ++++ src/OmniSharp.MSBuild/ProjectManager.cs | 7 +++++-- src/OmniSharp.MSBuild/ProjectSystem.cs | 8 +++++--- .../Services/Diagnostics/CodeCheckService.cs | 2 -- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/OmniSharp.Host/CompositionHostBuilder.cs b/src/OmniSharp.Host/CompositionHostBuilder.cs index cd747eebbf..d2cba27892 100644 --- a/src/OmniSharp.Host/CompositionHostBuilder.cs +++ b/src/OmniSharp.Host/CompositionHostBuilder.cs @@ -4,6 +4,7 @@ using System.Composition.Hosting.Core; using System.Linq; using System.Reflection; +using Microsoft.CodeAnalysis; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -42,6 +43,7 @@ public CompositionHost Build() var memoryCache = _serviceProvider.GetRequiredService(); var loggerFactory = _serviceProvider.GetRequiredService(); var assemblyLoader = _serviceProvider.GetRequiredService(); + var analyzerAssemblyLoader = _serviceProvider.GetRequiredService(); var environment = _serviceProvider.GetRequiredService(); var eventEmitter = _serviceProvider.GetRequiredService(); var dotNetCliService = _serviceProvider.GetRequiredService(); @@ -68,6 +70,7 @@ public CompositionHost Build() .WithProvider(MefValueProvider.From(options.CurrentValue)) .WithProvider(MefValueProvider.From(options.CurrentValue.FormattingOptions)) .WithProvider(MefValueProvider.From(assemblyLoader)) + .WithProvider(MefValueProvider.From(analyzerAssemblyLoader)) .WithProvider(MefValueProvider.From(dotNetCliService)) .WithProvider(MefValueProvider.From(metadataHelper)) .WithProvider(MefValueProvider.From(msbuildLocator)) @@ -148,6 +151,7 @@ public static IServiceProvider CreateDefaultServiceProvider(IOmniSharpEnvironmen // Caching services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddOptions(); services.AddSingleton(); diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 9701e6212a..4bba5ab6a8 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -52,6 +52,7 @@ public ProjectToUpdate(string filePath, bool allowAutoRestore) private readonly BufferBlock _queue; private readonly CancellationTokenSource _processLoopCancellation; private readonly Task _processLoopTask; + private readonly IAnalyzerAssemblyLoader _assemblyLoader; private bool _processingQueue; private readonly FileSystemNotificationCallback _onDirectoryFileChanged; @@ -66,7 +67,8 @@ public ProjectManager( ProjectLoader projectLoader, OmniSharpWorkspace workspace, CodeFixesForProjects codeFixesForProject, - RulesetsForProjects rulesetsForProjects) + RulesetsForProjects rulesetsForProjects, + IAnalyzerAssemblyLoader assemblyLoader) { _logger = loggerFactory.CreateLogger(); _eventEmitter = eventEmitter; @@ -81,6 +83,7 @@ public ProjectManager( _queue = new BufferBlock(); _processLoopCancellation = new CancellationTokenSource(); _processLoopTask = Task.Run(() => ProcessLoopAsync(_processLoopCancellation.Token)); + _assemblyLoader = assemblyLoader; _onDirectoryFileChanged = OnDirectoryFileChanged; _rulesetsForProjects = rulesetsForProjects; @@ -276,7 +279,7 @@ private void AddProject(ProjectFileInfo projectFileInfo) _projectFiles.Add(projectFileInfo); - var projectInfo = projectFileInfo.CreateProjectInfo(); + var projectInfo = projectFileInfo.CreateProjectInfo(_assemblyLoader); _codeFixesForProject.LoadFrom(projectInfo.Id.ToString(), projectFileInfo.Analyzers); diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 4f2e57cdde..b8fd529161 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -38,7 +38,7 @@ public class ProjectSystem : IProjectSystem private readonly CodeFixesForProjects _codeFixesForProjects; private readonly RulesetsForProjects _rulesetsForProjects; private readonly ILogger _logger; - + private readonly IAnalyzerAssemblyLoader _assemblyLoader; private readonly object _gate = new object(); private readonly Queue _projectsToProcess; @@ -66,7 +66,8 @@ public ProjectSystem( FileSystemHelper fileSystemHelper, ILoggerFactory loggerFactory, CodeFixesForProjects codeFixesForProjects, - RulesetsForProjects rulesetsForProjects) + RulesetsForProjects rulesetsForProjects, + IAnalyzerAssemblyLoader assemblyLoader) { _environment = environment; _workspace = workspace; @@ -82,6 +83,7 @@ public ProjectSystem( _rulesetsForProjects = rulesetsForProjects; _projectsToProcess = new Queue(); _logger = loggerFactory.CreateLogger(); + _assemblyLoader = assemblyLoader; } public void Initalize(IConfiguration configuration) @@ -100,7 +102,7 @@ public void Initalize(IConfiguration configuration) _packageDependencyChecker = new PackageDependencyChecker(_loggerFactory, _eventEmitter, _dotNetCli, _options); _loader = new ProjectLoader(_options, _environment.TargetDirectory, _propertyOverrides, _loggerFactory, _sdksPathResolver); - _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace, _codeFixesForProjects, _rulesetsForProjects); + _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace, _codeFixesForProjects, _rulesetsForProjects, _assemblyLoader); var initialProjectPaths = GetInitialProjectPaths(); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 15903bf415..384e09c9fb 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -1,5 +1,4 @@ using System.Composition; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -8,7 +7,6 @@ using OmniSharp.Mef; using OmniSharp.Models; using OmniSharp.Models.CodeCheck; -using OmniSharp.Models.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { From 014865ae8629710c022acb310da76c864c745298 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 8 Aug 2018 21:04:46 +0300 Subject: [PATCH 050/178] Implemented missing method. --- src/OmniSharp.Host/Services/AssemblyLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Host/Services/AssemblyLoader.cs b/src/OmniSharp.Host/Services/AssemblyLoader.cs index 28d24dd627..c843f9ac02 100644 --- a/src/OmniSharp.Host/Services/AssemblyLoader.cs +++ b/src/OmniSharp.Host/Services/AssemblyLoader.cs @@ -99,7 +99,7 @@ public Assembly LoadFrom(string assemblyPath, bool dontLockAssemblyOnDisk = fals public Assembly LoadFromPath(string fullPath) { - throw new NotImplementedException(); + return LoadFrom(fullPath); } } } From b0242cebce51c1422f52dc1ae3fb0ae3563497a8 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 8 Aug 2018 21:41:57 +0300 Subject: [PATCH 051/178] Some testfixes, however theres changes from master that require changes still. --- .../Services/Diagnostics/CodeCheckService.cs | 2 +- tests/TestUtility/TestServiceProvider.cs | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 384e09c9fb..563172f3a7 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -28,7 +28,7 @@ public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService rosl public async Task Handle(CodeCheckRequest request) { var projects = !string.IsNullOrEmpty(request.FileName) - ? _workspace.GetDocuments(request.FileName).Select(x => x.Project) + ? _workspace.GetDocuments(request.FileName).Where(x => x?.Project != null).Select(x => x.Project) : _workspace.CurrentSolution.Projects; var analyzerResults = diff --git a/tests/TestUtility/TestServiceProvider.cs b/tests/TestUtility/TestServiceProvider.cs index 7ef8b3e64a..bde757f190 100644 --- a/tests/TestUtility/TestServiceProvider.cs +++ b/tests/TestUtility/TestServiceProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -42,6 +43,7 @@ public TestServiceProvider( ); var assemblyLoader = new AssemblyLoader(loggerFactory); + var analyzerAssemblyLoader = new AssemblyLoader(loggerFactory); var msbuildLocator = MSBuildLocator.CreateStandAlone(loggerFactory, assemblyLoader, allowMonoPaths: false); var memoryCache = new MemoryCache(new MemoryCacheOptions()); dotNetCliService = dotNetCliService ?? new DotNetCliService(loggerFactory, eventEmitter); @@ -49,6 +51,7 @@ public TestServiceProvider( _services[typeof(ILoggerFactory)] = loggerFactory; _services[typeof(IOmniSharpEnvironment)] = environment; _services[typeof(IAssemblyLoader)] = assemblyLoader; + _services[typeof(IAnalyzerAssemblyLoader)] = analyzerAssemblyLoader; _services[typeof(IMemoryCache)] = memoryCache; _services[typeof(ISharedTextWriter)] = sharedTextWriter; _services[typeof(IMSBuildLocator)] = msbuildLocator; From bf382483968e30d6d18867e75d920a84fd3412f1 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 9 Aug 2018 20:15:17 +0300 Subject: [PATCH 052/178] Fix that is required for cake project system to work with diagnostics. --- .../Services/Diagnostics/CodeCheckService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 563172f3a7..ed8bdc9610 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -28,7 +28,7 @@ public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService rosl public async Task Handle(CodeCheckRequest request) { var projects = !string.IsNullOrEmpty(request.FileName) - ? _workspace.GetDocuments(request.FileName).Where(x => x?.Project != null).Select(x => x.Project) + ? new[] { _workspace.GetDocument(request.FileName).Project } : _workspace.CurrentSolution.Projects; var analyzerResults = From 9d7e2aec074a07923d1a66a78ffc9cf18946d177 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 9 Aug 2018 23:07:58 +0300 Subject: [PATCH 053/178] Removed obsolete hack. --- .../Workers/Diagnostics/RoslynAnalyzerService.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index f1af188400..2a5c0092e4 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -101,10 +101,6 @@ private async Task Worker(CancellationToken token) { while(!_initializationQueueDone) await Task.Delay(100); - // Since results are queried, it's better to return something even if that means litle wait. (this is hack...) - if (_workQueue.Count(x => projectIds.Any(pid => pid == x.Key)) == 0) - await Task.Delay(250); - var pendingWork = _workQueue .Where(x => projectIds.Any(pid => pid == x.Key)) .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) From 44949a233db6471ef85a4aada8d6cb0f0a57eda4 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 10 Aug 2018 08:09:12 +0300 Subject: [PATCH 054/178] More robust initialization routine. --- .../Diagnostics/RoslynAnalyzerService.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 2a5c0092e4..468b7ba0a6 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -30,7 +30,7 @@ public class RoslynAnalyzerService private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; - private bool _initializationQueueDone; + private CancellationTokenSource _initializationQueueDoneSource = new CancellationTokenSource(); private int _throttlingMs = 500; [ImportingConstructor] @@ -46,19 +46,20 @@ public RoslynAnalyzerService( workspace.WorkspaceChanged += OnWorkspaceChanged; - Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); + + _forwarder = forwarder; + _workspace = workspace; + _rulesetsForProjects = rulesetsForProjects; Task.Run(() => { - while (!workspace.Initialized) Task.Delay(500); + while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) Task.Delay(500); QueueForAnalysis(workspace.CurrentSolution.Projects); - _initializationQueueDone = true; + _initializationQueueDoneSource.Cancel(); _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); }); - _forwarder = forwarder; - _workspace = workspace; - _rulesetsForProjects = rulesetsForProjects; + Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); } private async Task Worker(CancellationToken token) @@ -99,12 +100,13 @@ private async Task Worker(CancellationToken token) public async Task> GetCurrentDiagnosticResult(IEnumerable projectIds) { - while(!_initializationQueueDone) await Task.Delay(100); + await Task.Delay(10 * 1000, _initializationQueueDoneSource.Token) + .ContinueWith(task => LogTimeouts(task, nameof(_initializationQueueDoneSource))); var pendingWork = _workQueue .Where(x => projectIds.Any(pid => pid == x.Key)) .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) - .ContinueWith(task => LogTimeouts(task, x.Key))); + .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))); await Task.WhenAll(pendingWork); @@ -113,9 +115,8 @@ private async Task Worker(CancellationToken token) .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))); } - private void LogTimeouts(Task task, ProjectId projectId) - { - if(!task.IsCanceled) _logger.LogError($"Timeout before work got ready for project {projectId}."); + private void LogTimeouts(Task task, string description) { + if(!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); } private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) From b3654d401649718ff69a244e2aea25d527d6bec5 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 10 Aug 2018 08:15:13 +0300 Subject: [PATCH 055/178] Small style tweaks. --- .../Workers/Diagnostics/RoslynAnalyzerService.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 468b7ba0a6..9961735b00 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -18,15 +18,11 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class RoslynAnalyzerService { private readonly ILogger _logger; - private readonly ConcurrentDictionary _workQueue = new ConcurrentDictionary(); - private readonly ConcurrentDictionary diagnostics)> _results = new ConcurrentDictionary diagnostics)>(); - private readonly IEnumerable _providers; - private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; @@ -46,7 +42,6 @@ public RoslynAnalyzerService( workspace.WorkspaceChanged += OnWorkspaceChanged; - _forwarder = forwarder; _workspace = workspace; _rulesetsForProjects = rulesetsForProjects; From bf9db1cbd2e7b78f7d9cdf4a8f41191f808eaada Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 10 Aug 2018 08:35:49 +0300 Subject: [PATCH 056/178] Removed empty class. --- .../CustomCodeFixesFacts.cs | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 tests/OmniSharp.Roslyn.CSharp.Tests/CustomCodeFixesFacts.cs diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomCodeFixesFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomCodeFixesFacts.cs deleted file mode 100644 index be4718f162..0000000000 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomCodeFixesFacts.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Xunit; - -namespace OmniSharp.Roslyn.CSharp.Tests -{ - public class CustomCodeFixesFacts - { - [Fact] - public void When_custom_diagnostics_is_executed_then_update_sources() - { - - } - } -} \ No newline at end of file From ee18d0f2f8856616db5bb88d1556c6e0c3507a72 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 19 Aug 2018 21:57:14 +0300 Subject: [PATCH 057/178] Some merge fixes. --- .../Services/Refactoring/V2/BaseCodeActionService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 33d86538b4..d17513b1e5 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -124,7 +124,7 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerable diagnostics, List codeActions) { - foreach (var codeFixProvider in GetSortedCodeFixProviders(document)) + foreach (var codeFixProvider in GetSortedCodeFixProviders()) { var fixableDiagnostics = diagnostics.Where(d => HasFix(codeFixProvider, d.Id)).ToImmutableArray(); @@ -144,16 +144,16 @@ private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerabl } } - private List GetSortedCodeFixProviders(Document document) + private List GetSortedCodeFixProviders() { var providerList = this.Providers.SelectMany(provider => provider.CodeFixProviders); return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); } - private List SortByTopologyIfPossibleOrReturnAsItWas(IEnumerable source) + private List GetSortedCodeRefactoringProviders() { var providerList = this.Providers.SelectMany(provider => provider.CodeRefactoringProviders); - return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); + return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); } private bool HasFix(CodeFixProvider codeFixProvider, string diagnosticId) From bf89628863cbb0aaf49372c21affe58fe25c2e4d Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 21 Aug 2018 08:08:22 +0300 Subject: [PATCH 058/178] Fixed mistake in wait routine. --- .../Workers/Diagnostics/RoslynAnalyzerService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index 9961735b00..a39fa5252d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -46,9 +46,10 @@ public RoslynAnalyzerService( _workspace = workspace; _rulesetsForProjects = rulesetsForProjects; - Task.Run(() => + Task.Run(async () => { - while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) Task.Delay(500); + while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); + QueueForAnalysis(workspace.CurrentSolution.Projects); _initializationQueueDoneSource.Cancel(); _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); From 8594fe8bedcfce4809aad1efbb0a8b86d787037b Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 21 Aug 2018 08:13:08 +0300 Subject: [PATCH 059/178] Small readibility update. --- .../Diagnostics/RoslynAnalyzerService.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index a39fa5252d..b0cc4e90d9 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -96,13 +96,9 @@ private async Task Worker(CancellationToken token) public async Task> GetCurrentDiagnosticResult(IEnumerable projectIds) { - await Task.Delay(10 * 1000, _initializationQueueDoneSource.Token) - .ContinueWith(task => LogTimeouts(task, nameof(_initializationQueueDoneSource))); + await WaitForInitialStartupWorkIfAny(); - var pendingWork = _workQueue - .Where(x => projectIds.Any(pid => pid == x.Key)) - .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) - .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))); + var pendingWork = WaitForPendingWorkIfNeededAndGetIt(projectIds); await Task.WhenAll(pendingWork); @@ -111,6 +107,20 @@ await Task.Delay(10 * 1000, _initializationQueueDoneSource.Token) .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))); } + private IEnumerable WaitForPendingWorkIfNeededAndGetIt(IEnumerable projectIds) + { + return _workQueue + .Where(x => projectIds.Any(pid => pid == x.Key)) + .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) + .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))); + } + + private Task WaitForInitialStartupWorkIfAny() + { + return Task.Delay(10 * 1000, _initializationQueueDoneSource.Token) + .ContinueWith(task => LogTimeouts(task, nameof(_initializationQueueDoneSource))); + } + private void LogTimeouts(Task task, string description) { if(!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); } From 65750552587ccb787eaa08e638d4e90c151f6d07 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 21 Aug 2018 08:15:29 +0300 Subject: [PATCH 060/178] Added todo. --- .../Services/Diagnostics/CodeCheckService.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 636272aaf5..60028a4b1f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -27,12 +27,21 @@ public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService rosl public async Task Handle(CodeCheckRequest request) { - var projects = !string.IsNullOrEmpty(request.FileName) + var projectsForAnalysis = !string.IsNullOrEmpty(request.FileName) ? new[] { _workspace.GetDocument(request.FileName).Project } : _workspace.CurrentSolution.Projects; + return await AnalyzerProjects(request, projectsForAnalysis); + + // SAVPEK TODO: Merge single file analysis and roslyn analysis before master. + //var quickFixes = await documents.FindDiagnosticLocationsAsync(_workspace); + //return new QuickFixResponse(quickFixes); + } + + private async Task AnalyzerProjects(CodeCheckRequest request, System.Collections.Generic.IEnumerable projects) + { var analyzerResults = - await _roslynAnalyzer.GetCurrentDiagnosticResult(projects.Select(x => x.Id)); + await _roslynAnalyzer.GetCurrentDiagnosticResult(projects.Select(x => x.Id)); var locations = analyzerResults .Where(x => (string.IsNullOrEmpty(request.FileName) || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) @@ -53,8 +62,6 @@ public async Task Handle(CodeCheckRequest request) return new QuickFixResponse( groupedByProjectWhenMultipleFrameworksAreUsed.Where(x => x.FileName != null)); - //var quickFixes = await documents.FindDiagnosticLocationsAsync(_workspace); - //return new QuickFixResponse(quickFixes); } } } From 85d424f8d12ef4d94ebed948ef7480af96729a7b Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 21 Aug 2018 20:19:21 +0300 Subject: [PATCH 061/178] Fixed support with misc files. --- .../Services/Diagnostics/CodeCheckService.cs | 12 +--------- .../Diagnostics/RoslynAnalyzerService.cs | 22 ++++++++++++++++++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 60028a4b1f..bfa80ac722 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -31,17 +31,7 @@ public async Task Handle(CodeCheckRequest request) ? new[] { _workspace.GetDocument(request.FileName).Project } : _workspace.CurrentSolution.Projects; - return await AnalyzerProjects(request, projectsForAnalysis); - - // SAVPEK TODO: Merge single file analysis and roslyn analysis before master. - //var quickFixes = await documents.FindDiagnosticLocationsAsync(_workspace); - //return new QuickFixResponse(quickFixes); - } - - private async Task AnalyzerProjects(CodeCheckRequest request, System.Collections.Generic.IEnumerable projects) - { - var analyzerResults = - await _roslynAnalyzer.GetCurrentDiagnosticResult(projects.Select(x => x.Id)); + var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult(projectsForAnalysis.Select(x => x.Id)); var locations = analyzerResults .Where(x => (string.IsNullOrEmpty(request.FileName) || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs index b0cc4e90d9..576d25309a 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs @@ -150,7 +150,15 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou { try { - var allAnalyzers = this._providers + // Only basic syntax check is available if file is miscellanous like orphan .cs file. + // Todo: Where this magic string should be moved? + if(project.Name == "MiscellaneousFiles") + { + await AnalyzeSingleMiscFilesProject(project); + return; + } + + var allAnalyzers = _providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); @@ -169,5 +177,17 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou workReadySource.Cancel(); } } + + private async Task AnalyzeSingleMiscFilesProject(Project project) + { + var syntaxTrees = await Task.WhenAll(project.Documents + .Select(async document => await document.GetSyntaxTreeAsync())); + + var results = syntaxTrees + .Select(x => x.GetDiagnostics()) + .SelectMany(x => x); + + _results[project.Id] = (project.Name, results); + } } } From 32b0c8599a80da7d204cd82bdfa983a6fc498ca9 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 24 Aug 2018 09:23:32 +0300 Subject: [PATCH 062/178] Restored null safety. --- .../Services/Diagnostics/CodeCheckService.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index bfa80ac722..ec50415a82 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -28,8 +28,8 @@ public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService rosl public async Task Handle(CodeCheckRequest request) { var projectsForAnalysis = !string.IsNullOrEmpty(request.FileName) - ? new[] { _workspace.GetDocument(request.FileName).Project } - : _workspace.CurrentSolution.Projects; + ? _workspace.GetDocuments(request.FileName).Where(x => x?.Project != null).Select(x => x.Project) + : new[] { _workspace.GetDocument(request.FileName).Project }; var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult(projectsForAnalysis.Select(x => x.Id)); From 39e33ecf60ad133ea665556726ed36781290fa3b Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 24 Aug 2018 10:10:46 +0300 Subject: [PATCH 063/178] Fixed mistake. --- .../Services/Diagnostics/CodeCheckService.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index ec50415a82..cc720216b1 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -28,13 +28,15 @@ public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService rosl public async Task Handle(CodeCheckRequest request) { var projectsForAnalysis = !string.IsNullOrEmpty(request.FileName) - ? _workspace.GetDocuments(request.FileName).Where(x => x?.Project != null).Select(x => x.Project) - : new[] { _workspace.GetDocument(request.FileName).Project }; + ? new[] { _workspace.GetDocument(request.FileName)?.Project } + : _workspace.CurrentSolution.Projects; - var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult(projectsForAnalysis.Select(x => x.Id)); + var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult( + projectsForAnalysis.Where(x => x != null).Select(x => x.Id)); var locations = analyzerResults - .Where(x => (string.IsNullOrEmpty(request.FileName) || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) + .Where(x => (string.IsNullOrEmpty(request.FileName) + || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) .Select(x => new { location = x.diagnostic.ToDiagnosticLocation(), From fa9f660b793e1a3d97d92cf7e6096323aecb98bd Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 28 Aug 2018 21:24:18 +0300 Subject: [PATCH 064/178] Review updates, however theres broken csx diagnostic tests still. --- .../Models/v1/QuickFix.cs | 2 +- .../Helpers/DiagnosticExtensions.cs | 44 +--- .../Services/Diagnostics/CodeCheckService.cs | 21 +- .../Diagnostics/DiagnosticsService.cs | 11 +- .../Refactoring/V2/BaseCodeActionService.cs | 14 +- .../Refactoring/V2/GetCodeActionsService.cs | 2 +- .../Refactoring/V2/RunCodeActionService.cs | 2 +- .../Diagnostics/CSharpDiagnosticService.cs | 244 +++++++++++------- .../Diagnostics/RoslynAnalyzerService.cs | 193 -------------- .../EndpointFacts.cs | 2 +- ...rFacts.cs => CustomRoslynAnalyzerFacts.cs} | 4 +- ...iagnosticsV2Facts.DiagnosticTestEmitter.cs | 27 +- .../DiagnosticsV2Facts.cs | 70 ++--- 13 files changed, 247 insertions(+), 389 deletions(-) delete mode 100644 src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs rename tests/OmniSharp.Roslyn.CSharp.Tests/{RoslynAnalyzerFacts.cs => CustomRoslynAnalyzerFacts.cs} (97%) diff --git a/src/OmniSharp.Abstractions/Models/v1/QuickFix.cs b/src/OmniSharp.Abstractions/Models/v1/QuickFix.cs index 70ec20d4b2..39bd7541ca 100644 --- a/src/OmniSharp.Abstractions/Models/v1/QuickFix.cs +++ b/src/OmniSharp.Abstractions/Models/v1/QuickFix.cs @@ -49,7 +49,7 @@ public override int GetHashCode() } public override string ToString() - => $"({Line}:{Column}) - ({EndLine}:{EndColumn})"; + => $"{Text} ({Line}:{Column}) - ({EndLine}:{EndColumn})"; public bool Contains(int line, int column) { diff --git a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs index 6f32cb801a..14cbd48c05 100644 --- a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs +++ b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs @@ -24,41 +24,21 @@ internal static DiagnosticLocation ToDiagnosticLocation(this Diagnostic diagnost }; } - internal static async Task> FindDiagnosticLocationsAsync(this IEnumerable documents, OmniSharpWorkspace workspace) + internal static IEnumerable DistinctDiagnosticLocationsByProject(this IEnumerable<(string projectName, Diagnostic diagnostic)> analyzerResults) { - if (documents == null || !documents.Any()) return Enumerable.Empty(); - - var items = new List(); - foreach (var document in documents) - { - IEnumerable diagnostics; - if (workspace.IsCapableOfSemanticDiagnostics(document)) + return analyzerResults + .Select(x => new { - var semanticModel = await document.GetSemanticModelAsync(); - diagnostics = semanticModel.GetDiagnostics(); - } - else + location = x.diagnostic.ToDiagnosticLocation(), + project = x.projectName + }) + .GroupBy(x => x.location) + .Select(x => { - var syntaxModel = await document.GetSyntaxTreeAsync(); - diagnostics = syntaxModel.GetDiagnostics(); - } - - foreach (var quickFix in diagnostics.Select(d => d.ToDiagnosticLocation())) - { - var existingQuickFix = items.FirstOrDefault(q => q.Equals(quickFix)); - if (existingQuickFix == null) - { - quickFix.Projects.Add(document.Project.Name); - items.Add(quickFix); - } - else - { - existingQuickFix.Projects.Add(document.Project.Name); - } - } - } - - return items; + var location = x.First().location; + location.Projects = x.Select(a => a.project).ToList(); + return location; + }); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index cc720216b1..893c333fa1 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -14,11 +14,11 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class CodeCheckService : IRequestHandler { private readonly OmniSharpWorkspace _workspace; - private readonly RoslynAnalyzerService _roslynAnalyzer; + private readonly CSharpDiagnosticService _roslynAnalyzer; private readonly ILogger _logger; [ImportingConstructor] - public CodeCheckService(OmniSharpWorkspace workspace, RoslynAnalyzerService roslynAnalyzer, ILoggerFactory loggerFactory) + public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticService roslynAnalyzer, ILoggerFactory loggerFactory) { _workspace = workspace; _roslynAnalyzer = roslynAnalyzer; @@ -37,23 +37,10 @@ public async Task Handle(CodeCheckRequest request) var locations = analyzerResults .Where(x => (string.IsNullOrEmpty(request.FileName) || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) - .Select(x => new - { - location = x.diagnostic.ToDiagnosticLocation(), - project = x.projectName - }); - - var groupedByProjectWhenMultipleFrameworksAreUsed = locations - .GroupBy(x => x.location) - .Select(x => - { - var location = x.First().location; - location.Projects = x.Select(a => a.project).ToList(); - return location; - }); + .DistinctDiagnosticLocationsByProject(); return new QuickFixResponse( - groupedByProjectWhenMultipleFrameworksAreUsed.Where(x => x.FileName != null)); + locations.Where(x => x.FileName != null)); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs index 9ed6cda2f6..74a7cf9b32 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs @@ -4,16 +4,15 @@ using Microsoft.CodeAnalysis; using OmniSharp.Mef; using OmniSharp.Models.Diagnostics; -using OmniSharp.Workers.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { [OmniSharpHandler(OmniSharpEndpoints.Diagnostics, LanguageNames.CSharp)] public class DiagnosticsService : IRequestHandler { - private readonly CSharpDiagnosticService _diagnostics; private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; + private readonly CSharpDiagnosticService _diagnostics; [ImportingConstructor] public DiagnosticsService(OmniSharpWorkspace workspace, DiagnosticEventForwarder forwarder, CSharpDiagnosticService diagnostics) @@ -30,11 +29,11 @@ public Task Handle(DiagnosticsRequest request) _forwarder.IsEnabled = true; } - var documents = request.FileName != null - ? new [] { request.FileName } - : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents.Select(x => x.FilePath)); + var projectsForAnalysis = !string.IsNullOrEmpty(request.FileName) + ? new[] { _workspace.GetDocument(request.FileName)?.Project } + : _workspace.CurrentSolution.Projects; - _diagnostics.QueueDiagnostics(documents.ToArray()); + _diagnostics.QueueForAnalysis(projectsForAnalysis.Where(x => x != null)); return Task.FromResult(new DiagnosticsResponse()); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index d17513b1e5..d7a8307434 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -25,7 +25,7 @@ public abstract class BaseCodeActionService : IRequestHandl protected readonly OmniSharpWorkspace Workspace; protected readonly IEnumerable Providers; protected readonly ILogger Logger; - private readonly RoslynAnalyzerService analyzers; + private readonly CSharpDiagnosticService analyzers; private readonly CodeFixesForProjects codeFixesForProject; private readonly MethodInfo _getNestedCodeActions; @@ -37,7 +37,7 @@ public abstract class BaseCodeActionService : IRequestHandl { "CS8019", "RemoveUnnecessaryImportsFixable" } }; - protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, RoslynAnalyzerService analyzers, CodeFixesForProjects codeFixesForProject) + protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, CSharpDiagnosticService analyzers, CodeFixesForProjects codeFixesForProject) { this.Workspace = workspace; this.Providers = providers; @@ -104,14 +104,12 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText) private async Task CollectCodeFixesActions(Document document, TextSpan span, List codeActions) { - var semanticModel = await document.GetSemanticModelAsync(); - var analyzers = await this.analyzers.GetCurrentDiagnosticResult(new [] { document.Project.Id }); - var groupedBySpan = semanticModel.GetDiagnostics() - .Concat(analyzers.Select(x => x.diagnostic)) - .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) - .GroupBy(diagnostic => diagnostic.Location.SourceSpan); + var groupedBySpan = + analyzers.Select(x => x.diagnostic) + .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) + .GroupBy(diagnostic => diagnostic.Location.SourceSpan); foreach (var diagnosticGroupedBySpan in groupedBySpan) { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs index 659a787f68..fcd5d0deb3 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs @@ -21,7 +21,7 @@ public GetCodeActionsService( CodeActionHelper helper, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - RoslynAnalyzerService analyzers, + CSharpDiagnosticService analyzers, CodeFixesForProjects codeFixesForProjects) : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index 36e5113759..99c9be589a 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -40,7 +40,7 @@ public RunCodeActionService( CodeActionHelper helper, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - RoslynAnalyzerService analyzers, + CSharpDiagnosticService analyzers, CodeFixesForProjects codeFixesForProjects) : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) { diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 03e733f669..8b9fb41828 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -1,152 +1,222 @@ +using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.Extensions.Logging; using OmniSharp.Helpers; using OmniSharp.Models.Diagnostics; -using OmniSharp.Roslyn; +using OmniSharp.Services; -namespace OmniSharp.Workers.Diagnostics +namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { - [Export, Shared] + [Shared] + [Export(typeof(CSharpDiagnosticService))] public class CSharpDiagnosticService { - private readonly ILogger _logger; - private readonly OmniSharpWorkspace _workspace; - private readonly object _lock = new object(); + private readonly ILogger _logger; + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); + private readonly ConcurrentDictionary diagnostics)> _results = + new ConcurrentDictionary diagnostics)>(); + private readonly IEnumerable _providers; private readonly DiagnosticEventForwarder _forwarder; - private bool _queueRunning = false; - private readonly ConcurrentQueue _openDocuments = new ConcurrentQueue(); + private readonly OmniSharpWorkspace _workspace; + private readonly RulesetsForProjects _rulesetsForProjects; + private CancellationTokenSource _initializationQueueDoneSource = new CancellationTokenSource(); + private int _throttlingMs = 500; [ImportingConstructor] - public CSharpDiagnosticService(OmniSharpWorkspace workspace, DiagnosticEventForwarder forwarder, ILoggerFactory loggerFactory) + public CSharpDiagnosticService( + OmniSharpWorkspace workspace, + [ImportMany] IEnumerable providers, + ILoggerFactory loggerFactory, + DiagnosticEventForwarder forwarder, + RulesetsForProjects rulesetsForProjects) { - _workspace = workspace; - _forwarder = forwarder; _logger = loggerFactory.CreateLogger(); + _providers = providers; + + workspace.WorkspaceChanged += OnWorkspaceChanged; - _workspace.WorkspaceChanged += OnWorkspaceChanged; + _forwarder = forwarder; + _workspace = workspace; + _rulesetsForProjects = rulesetsForProjects; + + Task.Run(async () => + { + while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); + + QueueForAnalysis(workspace.CurrentSolution.Projects); + _initializationQueueDoneSource.Cancel(); + _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); + }); + + Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) + private async Task Worker(CancellationToken token) { - if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged) + while (!token.IsCancellationRequested) { - var newDocument = changeEvent.NewSolution.GetDocument(changeEvent.DocumentId); - - this.EmitDiagnostics(newDocument.FilePath); - foreach (var document in _workspace.GetOpenDocumentIds().Select(x => _workspace.CurrentSolution.GetDocument(x))) + try + { + var currentWork = GetThrottledWork(); + await Task.WhenAll(currentWork.Select(x => Analyze(x.project, x.workReadySource, token))); + await Task.Delay(100, token); + } + catch (Exception ex) { - this.EmitDiagnostics(document.FilePath); + _logger.LogError($"Analyzer worker failed: {ex}"); } } } - public void QueueDiagnostics(params string[] documents) + private IEnumerable<(Project project, CancellationTokenSource workReadySource)> GetThrottledWork() { - this.EmitDiagnostics(documents); - } - - private void EmitDiagnostics(params string[] documents) - { - if (_forwarder.IsEnabled) + lock (_workQueue) { - foreach (var document in documents) - { - if (!_openDocuments.Contains(document)) - { - _openDocuments.Enqueue(document); - } - } + var currentWork = _workQueue + .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) + .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. + .Take(2) // Limit mount of work executed by once. This is needed on large solution... + .ToList(); - if (!_queueRunning && !_openDocuments.IsEmpty) + foreach (var workKey in currentWork.Select(x => x.Key)) { - this.ProcessQueue(); + _workQueue.TryRemove(workKey, out _); } + + return currentWork.Select(x => (x.Value.project, x.Value.workReadySource)); } } - private void ProcessQueue() + public async Task> GetCurrentDiagnosticResult(IEnumerable projectIds) + { + await WaitForInitialStartupWorkIfAny(); + + var pendingWork = WaitForPendingWorkIfNeededAndGetIt(projectIds); + + await Task.WhenAll(pendingWork); + + return _results + .Where(x => projectIds.Any(pid => pid == x.Key)) + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))); + } + + public void QueueForAnalysis(IEnumerable projects) { - lock (_lock) + foreach (var project in projects) { - _queueRunning = true; + _workQueue.AddOrUpdate(project.Id, + (modified: DateTime.UtcNow, project: project, new CancellationTokenSource()), + (_, oldValue) => (modified: DateTime.UtcNow, project: project, oldValue.workReadySource)); } + } - Task.Factory.StartNew(async () => - { - await Task.Delay(100); - await Dequeue(); + private IEnumerable WaitForPendingWorkIfNeededAndGetIt(IEnumerable projectIds) + { + return _workQueue + .Where(x => projectIds.Any(pid => pid == x.Key)) + .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) + .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))); + } - if (_openDocuments.IsEmpty) - { - lock (_lock) - { - _queueRunning = false; - } - } - else - { - this.ProcessQueue(); - } - }); + private Task WaitForInitialStartupWorkIfAny() + { + return Task.Delay(10 * 1000, _initializationQueueDoneSource.Token) + .ContinueWith(task => LogTimeouts(task, nameof(_initializationQueueDoneSource))); + } + + private void LogTimeouts(Task task, string description) + { + if (!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); + } + + private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) + { + if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged + || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded + || changeEvent.Kind == WorkspaceChangeKind.ProjectAdded) + { + var project = changeEvent.NewSolution.GetProject(changeEvent.ProjectId); + QueueForAnalysis(new[] { project }); + } } - private async Task Dequeue() + private async Task Analyze(Project project, CancellationTokenSource workReadySource, CancellationToken token) { - var tasks = new List>(); - for (var i = 0; i < 50; i++) + try { - if (_openDocuments.IsEmpty) + // Only basic syntax check is available if file is miscellanous like orphan .cs file. + // Todo: Where this magic string should be moved? + if (project.Name == "MiscellaneousFiles") { - break; + await AnalyzeSingleMiscFilesProject(project); + return; } - if (_openDocuments.TryDequeue(out var filePath)) + var allAnalyzers = _providers + .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) + .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); + + var compiled = await project.WithCompilationOptions( + _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) + .GetCompilationAsync(token); + + ImmutableArray results = ImmutableArray.Empty; + + if (allAnalyzers.ToImmutableArray().Any()) { - tasks.Add(this.ProcessNextItem(filePath)); + results = await compiled + .WithAnalyzers(allAnalyzers.ToImmutableArray()) // This cannot be invoked with empty analyzers list. + .GetAllDiagnosticsAsync(token); + } + else + { + results = compiled.GetDiagnostics(); } - } - if (!tasks.Any()) return; + _results[project.Id] = (project.Name, results); - var diagnosticResults = await Task.WhenAll(tasks.ToArray()); - if (diagnosticResults.Any()) + EmitDiagnostics(results); + } + finally { - var message = new DiagnosticMessage() - { - Results = diagnosticResults - }; - - this._forwarder.Forward(message); + workReadySource.Cancel(); } + } - if (_openDocuments.IsEmpty) + private void EmitDiagnostics(ImmutableArray results) + { + if (results.Any()) { - lock (_lock) + _forwarder.Forward(new DiagnosticMessage { - _queueRunning = false; - } - } - else - { - this.ProcessQueue(); + Results = results + .Select(x => x.ToDiagnosticLocation()) + .Where(x => x.FileName != null) + .GroupBy(x => x.FileName) + .Select(group => new DiagnosticResult { FileName = group.Key, QuickFixes = group.ToList() }) + }); } } - private async Task ProcessNextItem(string filePath) + private async Task AnalyzeSingleMiscFilesProject(Project project) { - var documents = _workspace.GetDocuments(filePath); - var items = await documents.FindDiagnosticLocationsAsync(_workspace); + var syntaxTrees = await Task.WhenAll(project.Documents + .Select(async document => await document.GetSyntaxTreeAsync())); - return new DiagnosticResult() - { - FileName = filePath, - QuickFixes = items - }; + var results = syntaxTrees + .Select(x => x.GetDiagnostics()) + .SelectMany(x => x); + + _results[project.Id] = (project.Name, results); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs deleted file mode 100644 index 576d25309a..0000000000 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/RoslynAnalyzerService.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.Extensions.Logging; -using OmniSharp.Services; - -namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics -{ - [Shared] - [Export(typeof(RoslynAnalyzerService))] - public class RoslynAnalyzerService - { - private readonly ILogger _logger; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); - private readonly ConcurrentDictionary diagnostics)> _results = - new ConcurrentDictionary diagnostics)>(); - private readonly IEnumerable _providers; - private readonly DiagnosticEventForwarder _forwarder; - private readonly OmniSharpWorkspace _workspace; - private readonly RulesetsForProjects _rulesetsForProjects; - private CancellationTokenSource _initializationQueueDoneSource = new CancellationTokenSource(); - private int _throttlingMs = 500; - - [ImportingConstructor] - public RoslynAnalyzerService( - OmniSharpWorkspace workspace, - [ImportMany] IEnumerable providers, - ILoggerFactory loggerFactory, - DiagnosticEventForwarder forwarder, - RulesetsForProjects rulesetsForProjects) - { - _logger = loggerFactory.CreateLogger(); - _providers = providers; - - workspace.WorkspaceChanged += OnWorkspaceChanged; - - _forwarder = forwarder; - _workspace = workspace; - _rulesetsForProjects = rulesetsForProjects; - - Task.Run(async () => - { - while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); - - QueueForAnalysis(workspace.CurrentSolution.Projects); - _initializationQueueDoneSource.Cancel(); - _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); - }); - - Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); - } - - private async Task Worker(CancellationToken token) - { - while (!token.IsCancellationRequested) - { - try - { - var currentWork = GetThrottledWork(); - await Task.WhenAll(currentWork.Select(x => Analyze(x.project, x.workReadySource, token))); - await Task.Delay(100, token); - } - catch (Exception ex) - { - _logger.LogError($"Analyzer worker failed: {ex}"); - } - } - } - - private IEnumerable<(Project project, CancellationTokenSource workReadySource)> GetThrottledWork() - { - lock (_workQueue) - { - var currentWork = _workQueue - .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) - .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. - .Take(2) // Limit mount of work executed by once. This is needed on large solution... - .ToList(); - - foreach(var workKey in currentWork.Select(x => x.Key)) - { - _workQueue.TryRemove(workKey, out _); - } - - return currentWork.Select(x => (x.Value.project, x.Value.workReadySource)); - } - } - - public async Task> GetCurrentDiagnosticResult(IEnumerable projectIds) - { - await WaitForInitialStartupWorkIfAny(); - - var pendingWork = WaitForPendingWorkIfNeededAndGetIt(projectIds); - - await Task.WhenAll(pendingWork); - - return _results - .Where(x => projectIds.Any(pid => pid == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))); - } - - private IEnumerable WaitForPendingWorkIfNeededAndGetIt(IEnumerable projectIds) - { - return _workQueue - .Where(x => projectIds.Any(pid => pid == x.Key)) - .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) - .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))); - } - - private Task WaitForInitialStartupWorkIfAny() - { - return Task.Delay(10 * 1000, _initializationQueueDoneSource.Token) - .ContinueWith(task => LogTimeouts(task, nameof(_initializationQueueDoneSource))); - } - - private void LogTimeouts(Task task, string description) { - if(!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); - } - - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) - { - if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged - || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded - || changeEvent.Kind == WorkspaceChangeKind.ProjectAdded) - { - var project = changeEvent.NewSolution.GetProject(changeEvent.ProjectId); - QueueForAnalysis(new[] { project }); - } - } - - private void QueueForAnalysis(IEnumerable projects) - { - foreach(var project in projects) - { - _workQueue.AddOrUpdate(project.Id, - (modified: DateTime.UtcNow, project: project, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, project: project, oldValue.workReadySource)); - } - } - - private async Task Analyze(Project project, CancellationTokenSource workReadySource, CancellationToken token) - { - try - { - // Only basic syntax check is available if file is miscellanous like orphan .cs file. - // Todo: Where this magic string should be moved? - if(project.Name == "MiscellaneousFiles") - { - await AnalyzeSingleMiscFilesProject(project); - return; - } - - var allAnalyzers = _providers - .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) - .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); - - var compiled = await project.WithCompilationOptions( - _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) - .GetCompilationAsync(token); - - var results = await compiled - .WithAnalyzers(allAnalyzers.ToImmutableArray()) - .GetAllDiagnosticsAsync(token); - - _results[project.Id] = (project.Name, results); - } - finally - { - workReadySource.Cancel(); - } - } - - private async Task AnalyzeSingleMiscFilesProject(Project project) - { - var syntaxTrees = await Task.WhenAll(project.Documents - .Select(async document => await document.GetSyntaxTreeAsync())); - - var results = syntaxTrees - .Select(x => x.GetDiagnostics()) - .SelectMany(x => x); - - _results[project.Id] = (project.Name, results); - } - } -} diff --git a/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs b/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs index 9fd6d7647a..03120764ac 100644 --- a/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs +++ b/tests/OmniSharp.MiscellaneousFiles.Tests/EndpointFacts.cs @@ -37,7 +37,7 @@ public async Task Returns_only_syntactic_diagnotics() var request = new CodeCheckRequest() { FileName = filePath }; var actual = await host.GetResponse(OmniSharpEndpoints.CodeCheck, request); Assert.Single(actual.QuickFixes); - Assert.Equal("; expected", actual.QuickFixes.First().Text); + Assert.Equal("; expected (CS1002)", actual.QuickFixes.First().Text); } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs similarity index 97% rename from tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs rename to tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index cea1d4c1e8..fd7522024f 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/RoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -14,7 +14,7 @@ namespace OmniSharp.Roslyn.CSharp.Tests { - public class RoslynAnalyzerFacts : AbstractSingleRequestHandlerTestFixture + public class CustomRoslynAnalyzerFacts : AbstractSingleRequestHandlerTestFixture { protected override string EndpointName => OmniSharpEndpoints.CodeCheck; @@ -89,7 +89,7 @@ private void AnalyzeSymbol(SymbolAnalysisContext context) } } - public RoslynAnalyzerFacts(ITestOutputHelper output, SharedOmniSharpHostFixture sharedOmniSharpHostFixture) + public CustomRoslynAnalyzerFacts(ITestOutputHelper output, SharedOmniSharpHostFixture sharedOmniSharpHostFixture) : base(output, sharedOmniSharpHostFixture) { } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs index 456a4b3a00..addb08b870 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using OmniSharp.Eventing; using OmniSharp.Models.Diagnostics; @@ -9,21 +10,33 @@ public partial class DiagnosticsV2Facts { private class DiagnosticTestEmitter : IEventEmitter { - private readonly IList _messages; + public readonly List Messages = new List(); private readonly TaskCompletionSource _tcs; - public Task Emitted => _tcs.Task; + public async Task WaitForEmitted(int expectedCount = 1) + { + // May seem hacky but nothing is more painfull to debug than infinite hanging test ... + for(int i = 0; i < 100; i++) + { + if(Messages.Count == expectedCount) + { + return; + } + + await Task.Delay(50); + } + + throw new InvalidOperationException($"Timeout reached before expected event count reached, expected '{expectedCount}' got '{Messages.Count}' "); + } - public DiagnosticTestEmitter(IList messages) + public DiagnosticTestEmitter() { - _messages = messages; _tcs = new TaskCompletionSource(); } public void Emit(string kind, object args) { - _messages.Add((DiagnosticMessage)args); - _tcs.TrySetResult(null); + Messages.Add((DiagnosticMessage)args); } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index 08d0d76d5c..71b3ce32c4 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using OmniSharp.Models.Diagnostics; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; -using OmniSharp.Workers.Diagnostics; +using OmniSharp.Services; using TestUtility; using Xunit; using Xunit.Abstractions; @@ -18,63 +18,65 @@ public DiagnosticsV2Facts(ITestOutputHelper output, SharedOmniSharpHostFixture s } [Theory] - [InlineData("a.cs")] - [InlineData("a.csx")] - public async Task CodeCheckSpecifiedFileOnly(string filename) + [InlineData("a.cs", 2)] + [InlineData("a.csx", 1)] + public async Task CodeCheckSpecifiedFileOnly(string filename, int compilationTargetsCount) { + SharedOmniSharpTestHost.ClearWorkspace(); + var testFile = new TestFile(filename, "class C { int n = true; }"); - SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - var messages = new List(); - var emitter = new DiagnosticTestEmitter(messages); + var emitter = new DiagnosticTestEmitter(); var forwarder = new DiagnosticEventForwarder(emitter) { IsEnabled = true }; - var service = new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, forwarder, this.LoggerFactory); - service.QueueDiagnostics(filename); + var service = CreateDiagnosticService(forwarder); + SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - await emitter.Emitted; + var controller = new DiagnosticsService(SharedOmniSharpTestHost.Workspace, forwarder, service); + var response = await controller.Handle(new DiagnosticsRequest { FileName = testFile.FileName }); - Assert.Single(messages); - var message = messages.First(); + await emitter.WaitForEmitted(expectedCount: compilationTargetsCount); + + Assert.Equal(compilationTargetsCount, emitter.Messages.Count()); + var message = emitter.Messages.First(); Assert.Single(message.Results); var result = message.Results.First(); Assert.Single(result.QuickFixes); Assert.Equal(filename, result.FileName); } + private CSharpDiagnosticService CreateDiagnosticService(DiagnosticEventForwarder forwarder) + { + return new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects()); + } + [Theory] - [InlineData("a.cs", "b.cs")] - [InlineData("a.csx", "b.csx")] - public async Task CheckAllFiles(string filename1, string filename2) + [InlineData("a.cs", "b.cs", 2)] + [InlineData("a.csx", "b.csx", 2)] + public async Task CheckAllFiles(string filename1, string filename2, int compilationTargetsCount) { + SharedOmniSharpTestHost.ClearWorkspace(); + var testFile1 = new TestFile(filename1, "class C1 { int n = true; }"); var testFile2 = new TestFile(filename2, "class C2 { int n = true; }"); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile1, testFile2); - var messages = new List(); - var emitter = new DiagnosticTestEmitter(messages); + var emitter = new DiagnosticTestEmitter(); var forwarder = new DiagnosticEventForwarder(emitter); - var service = new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, forwarder, this.LoggerFactory); + var service = CreateDiagnosticService(forwarder); var controller = new DiagnosticsService(SharedOmniSharpTestHost.Workspace, forwarder, service); var response = await controller.Handle(new DiagnosticsRequest()); - await emitter.Emitted; + await emitter.WaitForEmitted(expectedCount: compilationTargetsCount); - Assert.Single(messages); - var message = messages.First(); - Assert.Equal(2, message.Results.Count()); - - var a = message.Results.First(x => x.FileName == filename1); - Assert.Single(a.QuickFixes); - Assert.Equal(filename1, a.FileName); - - var b = message.Results.First(x => x.FileName == filename2); - Assert.Single(b.QuickFixes); - Assert.Equal(filename2, b.FileName); + Assert.Equal(compilationTargetsCount, emitter.Messages.Count()); + Assert.Equal(2, emitter.Messages.First().Results.Count()); + Assert.Single(emitter.Messages.First().Results.First().QuickFixes); + Assert.Single(emitter.Messages.First().Results.Skip(1).First().QuickFixes); } [Theory] @@ -82,13 +84,15 @@ public async Task CheckAllFiles(string filename1, string filename2) [InlineData("a.csx", "b.csx")] public async Task EnablesWhenEndPointIsHit(string filename1, string filename2) { + SharedOmniSharpTestHost.ClearWorkspace(); + var testFile1 = new TestFile(filename1, "class C1 { int n = true; }"); var testFile2 = new TestFile(filename2, "class C2 { int n = true; }"); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile1, testFile2); - var messages = new List(); - var emitter = new DiagnosticTestEmitter(messages); + + var emitter = new DiagnosticTestEmitter(); var forwarder = new DiagnosticEventForwarder(emitter); - var service = new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, forwarder, this.LoggerFactory); + var service = CreateDiagnosticService(forwarder); var controller = new DiagnosticsService(SharedOmniSharpTestHost.Workspace, forwarder, service); var response = await controller.Handle(new DiagnosticsRequest()); From e5ef6b3c94c0566cb596d7f97d83cd78b3df9b03 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 31 Aug 2018 18:19:59 +0300 Subject: [PATCH 065/178] Fixed custom code actions. --- .../Services/Refactoring/V2/BaseCodeActionService.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index d7a8307434..e7c4deea89 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -122,7 +122,7 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerable diagnostics, List codeActions) { - foreach (var codeFixProvider in GetSortedCodeFixProviders()) + foreach (var codeFixProvider in GetSortedCodeFixProviders(document)) { var fixableDiagnostics = diagnostics.Where(d => HasFix(codeFixProvider, d.Id)).ToImmutableArray(); @@ -142,9 +142,12 @@ private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerabl } } - private List GetSortedCodeFixProviders() + private List GetSortedCodeFixProviders(Document document) { - var providerList = this.Providers.SelectMany(provider => provider.CodeFixProviders); + var providerList = + this.Providers.SelectMany(provider => provider.CodeFixProviders) + .Concat(codeFixesForProject.GetAllCodeFixesForProject(document.Project.Id.ToString())); + return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); } From c5c92f0ede610c883522b277adb49d3364746ce7 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 31 Aug 2018 19:04:04 +0300 Subject: [PATCH 066/178] Testfixes. --- .../Models/v1/Diagnostics/DiagnosticResult.cs | 7 ++++- ...iagnosticsV2Facts.DiagnosticTestEmitter.cs | 10 ++++-- .../DiagnosticsV2Facts.cs | 31 +++++++------------ 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/OmniSharp.Abstractions/Models/v1/Diagnostics/DiagnosticResult.cs b/src/OmniSharp.Abstractions/Models/v1/Diagnostics/DiagnosticResult.cs index 2429f4801b..07d2f4ce12 100644 --- a/src/OmniSharp.Abstractions/Models/v1/Diagnostics/DiagnosticResult.cs +++ b/src/OmniSharp.Abstractions/Models/v1/Diagnostics/DiagnosticResult.cs @@ -6,5 +6,10 @@ public class DiagnosticResult { public string FileName { get; set; } public IEnumerable QuickFixes { get; set; } + + public override string ToString() + { + return $"{FileName} -> {string.Join(", ", QuickFixes)}"; + } } -} \ No newline at end of file +} diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs index addb08b870..8945518f14 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; using OmniSharp.Eventing; using OmniSharp.Models.Diagnostics; @@ -13,12 +15,14 @@ private class DiagnosticTestEmitter : IEventEmitter public readonly List Messages = new List(); private readonly TaskCompletionSource _tcs; - public async Task WaitForEmitted(int expectedCount = 1) + public async Task ExpectForEmitted(Expression> predicate) { + var asCompiledPredicate = predicate.Compile(); + // May seem hacky but nothing is more painfull to debug than infinite hanging test ... for(int i = 0; i < 100; i++) { - if(Messages.Count == expectedCount) + if(Messages.Any(m => asCompiledPredicate(m))) { return; } @@ -26,7 +30,7 @@ public async Task WaitForEmitted(int expectedCount = 1) await Task.Delay(50); } - throw new InvalidOperationException($"Timeout reached before expected event count reached, expected '{expectedCount}' got '{Messages.Count}' "); + throw new InvalidOperationException($"Timeout reached before expected event count reached before prediction {predicate} came true, current diagnostics '{String.Join(";", Messages.SelectMany(x => x.Results))}'"); } public DiagnosticTestEmitter() diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index 71b3ce32c4..722296a615 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -18,9 +18,9 @@ public DiagnosticsV2Facts(ITestOutputHelper output, SharedOmniSharpHostFixture s } [Theory] - [InlineData("a.cs", 2)] - [InlineData("a.csx", 1)] - public async Task CodeCheckSpecifiedFileOnly(string filename, int compilationTargetsCount) + [InlineData("a.cs")] + [InlineData("a.csx")] + public async Task CodeCheckSpecifiedFileOnly(string filename) { SharedOmniSharpTestHost.ClearWorkspace(); @@ -38,14 +38,7 @@ public async Task CodeCheckSpecifiedFileOnly(string filename, int compilationTar var controller = new DiagnosticsService(SharedOmniSharpTestHost.Workspace, forwarder, service); var response = await controller.Handle(new DiagnosticsRequest { FileName = testFile.FileName }); - await emitter.WaitForEmitted(expectedCount: compilationTargetsCount); - - Assert.Equal(compilationTargetsCount, emitter.Messages.Count()); - var message = emitter.Messages.First(); - Assert.Single(message.Results); - var result = message.Results.First(); - Assert.Single(result.QuickFixes); - Assert.Equal(filename, result.FileName); + await emitter.ExpectForEmitted(msg => msg.Results.Any(m => m.FileName == filename)); } private CSharpDiagnosticService CreateDiagnosticService(DiagnosticEventForwarder forwarder) @@ -54,9 +47,9 @@ private CSharpDiagnosticService CreateDiagnosticService(DiagnosticEventForwarder } [Theory] - [InlineData("a.cs", "b.cs", 2)] - [InlineData("a.csx", "b.csx", 2)] - public async Task CheckAllFiles(string filename1, string filename2, int compilationTargetsCount) + [InlineData("a.cs", "b.cs")] + [InlineData("a.csx", "b.csx")] + public async Task CheckAllFiles(string filename1, string filename2) { SharedOmniSharpTestHost.ClearWorkspace(); @@ -71,12 +64,10 @@ public async Task CheckAllFiles(string filename1, string filename2, int compilat var controller = new DiagnosticsService(SharedOmniSharpTestHost.Workspace, forwarder, service); var response = await controller.Handle(new DiagnosticsRequest()); - await emitter.WaitForEmitted(expectedCount: compilationTargetsCount); - - Assert.Equal(compilationTargetsCount, emitter.Messages.Count()); - Assert.Equal(2, emitter.Messages.First().Results.Count()); - Assert.Single(emitter.Messages.First().Results.First().QuickFixes); - Assert.Single(emitter.Messages.First().Results.Skip(1).First().QuickFixes); + await emitter.ExpectForEmitted(msg => msg.Results + .Any(r => r.FileName == filename1 && r.QuickFixes.Count() == 1)); + await emitter.ExpectForEmitted(msg => msg.Results + .Any(r => r.FileName == filename2 && r.QuickFixes.Count() == 1)); } [Theory] From 5b9b2658c03dcb914bac88f86399b1363a8a8773 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 9 Sep 2018 19:03:32 +0300 Subject: [PATCH 067/178] Small review fixes and simplified assembly loader algorithm. --- .../ProjectFile/ProjectFileInfoExtensions.cs | 21 +++++-------------- src/OmniSharp.MSBuild/ProjectManager.cs | 4 ++-- src/OmniSharp.MSBuild/ProjectSystem.cs | 4 ++-- .../Refactoring/V2/BaseCodeActionService.cs | 4 ++-- .../Refactoring/V2/CodeFixesForProjects.cs | 6 +++--- .../Refactoring/V2/GetCodeActionsService.cs | 2 +- .../Refactoring/V2/RunCodeActionService.cs | 2 +- 7 files changed, 16 insertions(+), 27 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs index 05b9eba131..740bb91b85 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfoExtensions.cs @@ -57,23 +57,12 @@ public static ProjectInfo CreateProjectInfo(this ProjectFileInfo projectFileInfo private static IEnumerable ResolveAnalyzerReferencesForProject(ProjectFileInfo projectFileInfo, IAnalyzerAssemblyLoader analyzerAssemblyLoader) { - return projectFileInfo.Analyzers - .GroupBy(x => Path.GetDirectoryName(x)) - .SelectMany(singleAnalyzerPackageGroup => - { - // Is there better way to figure out entry assembly for specific nuget analyzer package? And is there even entry assembly or is better way to just lookup all referenced assemblies? - return singleAnalyzerPackageGroup - .Where(x => x.EndsWith("analyzers.dll", ignoreCase: true, CultureInfo.InvariantCulture)) - .Select(analyzerMainAssembly => - { - foreach(var assembly in singleAnalyzerPackageGroup) - { - analyzerAssemblyLoader.AddDependencyLocation(assembly); - }; + foreach(var analyzerAssemblyPath in projectFileInfo.Analyzers.Distinct()) + { + analyzerAssemblyLoader.AddDependencyLocation(analyzerAssemblyPath); + } - return new AnalyzerFileReference(analyzerMainAssembly, analyzerAssemblyLoader); - }); - }); + return projectFileInfo.Analyzers.Select(analyzerCandicatePath => new AnalyzerFileReference(analyzerCandicatePath, analyzerAssemblyLoader)); } } } diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 4bba5ab6a8..45d1c1bb04 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -47,7 +47,7 @@ public ProjectToUpdate(string filePath, bool allowAutoRestore) private readonly HashSet _failedToLoadProjectFiles; private readonly ProjectLoader _projectLoader; private readonly OmniSharpWorkspace _workspace; - private readonly CodeFixesForProjects _codeFixesForProject; + private readonly CodeFixCacheForProjects _codeFixesForProject; private const int LoopDelay = 100; // milliseconds private readonly BufferBlock _queue; private readonly CancellationTokenSource _processLoopCancellation; @@ -66,7 +66,7 @@ public ProjectManager( PackageDependencyChecker packageDependencyChecker, ProjectLoader projectLoader, OmniSharpWorkspace workspace, - CodeFixesForProjects codeFixesForProject, + CodeFixCacheForProjects codeFixesForProject, RulesetsForProjects rulesetsForProjects, IAnalyzerAssemblyLoader assemblyLoader) { diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index 0715d8c5fc..63d4ab19ff 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -36,7 +36,7 @@ public class ProjectSystem : IProjectSystem private readonly IFileSystemWatcher _fileSystemWatcher; private readonly FileSystemHelper _fileSystemHelper; private readonly ILoggerFactory _loggerFactory; - private readonly CodeFixesForProjects _codeFixesForProjects; + private readonly CodeFixCacheForProjects _codeFixesForProjects; private readonly RulesetsForProjects _rulesetsForProjects; private readonly ILogger _logger; private readonly IAnalyzerAssemblyLoader _assemblyLoader; @@ -66,7 +66,7 @@ public ProjectSystem( IFileSystemWatcher fileSystemWatcher, FileSystemHelper fileSystemHelper, ILoggerFactory loggerFactory, - CodeFixesForProjects codeFixesForProjects, + CodeFixCacheForProjects codeFixesForProjects, RulesetsForProjects rulesetsForProjects, IAnalyzerAssemblyLoader assemblyLoader) { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index e7c4deea89..cf54864392 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -26,7 +26,7 @@ public abstract class BaseCodeActionService : IRequestHandl protected readonly IEnumerable Providers; protected readonly ILogger Logger; private readonly CSharpDiagnosticService analyzers; - private readonly CodeFixesForProjects codeFixesForProject; + private readonly CodeFixCacheForProjects codeFixesForProject; private readonly MethodInfo _getNestedCodeActions; protected Lazy> OrderedCodeRefactoringProviders; @@ -37,7 +37,7 @@ public abstract class BaseCodeActionService : IRequestHandl { "CS8019", "RemoveUnnecessaryImportsFixable" } }; - protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, CSharpDiagnosticService analyzers, CodeFixesForProjects codeFixesForProject) + protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, CSharpDiagnosticService analyzers, CodeFixCacheForProjects codeFixesForProject) { this.Workspace = workspace; this.Providers = providers; diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs index 8e9a9832a9..4dc19bdd9a 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs @@ -11,14 +11,14 @@ namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 { [Shared] - [Export(typeof(CodeFixesForProjects))] - public class CodeFixesForProjects + [Export(typeof(CodeFixCacheForProjects))] + public class CodeFixCacheForProjects { private readonly ConcurrentDictionary> _codeFixCache = new ConcurrentDictionary>(); private readonly IAssemblyLoader _assemblyLoader; [ImportingConstructor] - public CodeFixesForProjects(IAssemblyLoader assemblyLoader) + public CodeFixCacheForProjects(IAssemblyLoader assemblyLoader) { _assemblyLoader = assemblyLoader; } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs index fcd5d0deb3..a2b9d3ba6f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs @@ -22,7 +22,7 @@ public GetCodeActionsService( [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, CSharpDiagnosticService analyzers, - CodeFixesForProjects codeFixesForProjects) + CodeFixCacheForProjects codeFixesForProjects) : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) { } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index 99c9be589a..9fc92f4898 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -41,7 +41,7 @@ public RunCodeActionService( [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, CSharpDiagnosticService analyzers, - CodeFixesForProjects codeFixesForProjects) + CodeFixCacheForProjects codeFixesForProjects) : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) { _loader = loader; From 4989125cb6900e666ae117e34d379ba13f8e94e0 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 9 Sep 2018 20:14:53 +0300 Subject: [PATCH 068/178] Mergefix. --- .../ProjectFile/ProjectFileInfo.ProjectData.cs | 3 ++- src/OmniSharp.MSBuild/ProjectManager.cs | 1 - src/OmniSharp.MSBuild/ProjectSystem.cs | 3 +-- tests/TestUtility/TestServiceProvider.cs | 17 +---------------- 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs index d7b586ffe1..bfe9382493 100644 --- a/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs +++ b/src/OmniSharp.MSBuild/ProjectFile/ProjectFileInfo.ProjectData.cs @@ -125,7 +125,8 @@ private ProjectData( ImmutableArray projectReferences, ImmutableArray references, ImmutableArray packageReferences, - ImmutableArray analyzers) + ImmutableArray analyzers, + RuleSet ruleset) : this(guid, name, assemblyName, targetPath, outputPath, intermediateOutputPath, projectAssetsFile, configuration, platform, targetFramework, targetFrameworks, outputKind, languageVersion, allowUnsafeCode, documentationFile, preprocessorSymbolNames, suppressedDiagnosticIds, signAssembly, assemblyOriginatorKeyFile, ruleset) diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 03ad8677f8..2dd696ae1b 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -89,7 +89,6 @@ public ProjectManager( _processLoopCancellation = new CancellationTokenSource(); _processLoopTask = Task.Run(() => ProcessLoopAsync(_processLoopCancellation.Token)); _assemblyLoader = assemblyLoader; - _onDirectoryFileChanged = OnDirectoryFileChanged; _rulesetsForProjects = rulesetsForProjects; } diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index ce26495fed..a4b33f306e 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -108,8 +108,7 @@ public void Initalize(IConfiguration configuration) _packageDependencyChecker = new PackageDependencyChecker(_loggerFactory, _eventEmitter, _dotNetCli, _options); _loader = new ProjectLoader(_options, _environment.TargetDirectory, _propertyOverrides, _loggerFactory, _sdksPathResolver); - _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace, _codeFixesForProjects, _rulesetsForProjects, _assemblyLoader); - _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace, _eventSinks); + _manager = new ProjectManager(_loggerFactory, _eventEmitter, _fileSystemWatcher, _metadataFileReferenceCache, _packageDependencyChecker, _loader, _workspace, _codeFixesForProjects, _rulesetsForProjects, _assemblyLoader, _eventSinks); var initialProjectPaths = GetInitialProjectPaths(); diff --git a/tests/TestUtility/TestServiceProvider.cs b/tests/TestUtility/TestServiceProvider.cs index f81dfb3f39..8bcf59e05b 100644 --- a/tests/TestUtility/TestServiceProvider.cs +++ b/tests/TestUtility/TestServiceProvider.cs @@ -48,6 +48,7 @@ private TestServiceProvider( AddService(dotNetCliService); AddService(configuration); AddService(optionsMonitor); + _services[typeof(IAnalyzerAssemblyLoader)] = assemblyLoader; } public static IServiceProvider Create( @@ -165,22 +166,6 @@ private static IOptionsMonitor CreateOptionsMonitor(IConfigura postConfigures: Enumerable.Empty>() ); - var assemblyLoader = new AssemblyLoader(loggerFactory); - var analyzerAssemblyLoader = new AssemblyLoader(loggerFactory); - var msbuildLocator = MSBuildLocator.CreateStandAlone(loggerFactory, assemblyLoader, allowMonoPaths: false); - var memoryCache = new MemoryCache(new MemoryCacheOptions()); - dotNetCliService = dotNetCliService ?? new DotNetCliService(loggerFactory, eventEmitter); - - _services[typeof(ILoggerFactory)] = loggerFactory; - _services[typeof(IOmniSharpEnvironment)] = environment; - _services[typeof(IAssemblyLoader)] = assemblyLoader; - _services[typeof(IAnalyzerAssemblyLoader)] = analyzerAssemblyLoader; - _services[typeof(IMemoryCache)] = memoryCache; - _services[typeof(ISharedTextWriter)] = sharedTextWriter; - _services[typeof(IMSBuildLocator)] = msbuildLocator; - _services[typeof(IEventEmitter)] = eventEmitter; - _services[typeof(IDotNetCliService)] = dotNetCliService; - return new OptionsMonitor( factory, sources: Enumerable.Empty>(), From 16ef3bb879386e10e8c3eb1dbe2f00f4c754fabd Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 10 Sep 2018 19:15:22 +0300 Subject: [PATCH 069/178] Fixed changed MiscFile project name. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 8b9fb41828..298255e084 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -155,7 +155,7 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou { // Only basic syntax check is available if file is miscellanous like orphan .cs file. // Todo: Where this magic string should be moved? - if (project.Name == "MiscellaneousFiles") + if (project.Name == "MiscellaneousFiles.csproj") { await AnalyzeSingleMiscFilesProject(project); return; From 2360ee2b99ce8949ed9030d2013904ac9580eccd Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 10 Sep 2018 19:39:16 +0300 Subject: [PATCH 070/178] Renamed service with better name and fixed missing language for analysis. --- src/OmniSharp.MSBuild/ProjectManager.cs | 4 ++-- src/OmniSharp.MSBuild/ProjectSystem.cs | 4 ++-- .../Refactoring/V2/BaseCodeActionService.cs | 4 ++-- .../Refactoring/V2/CodeFixesForProjects.cs | 14 +++++++------- .../Refactoring/V2/GetCodeActionsService.cs | 2 +- .../Refactoring/V2/RunCodeActionService.cs | 2 +- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 2dd696ae1b..355c640d1a 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -49,7 +49,7 @@ public ProjectToUpdate(string filePath, bool allowAutoRestore) private readonly HashSet _failedToLoadProjectFiles; private readonly ProjectLoader _projectLoader; private readonly OmniSharpWorkspace _workspace; - private readonly CodeFixCacheForProjects _codeFixesForProject; + private readonly CachingCodeFixProviderForProjects _codeFixesForProject; private readonly ImmutableArray _eventSinks; private const int LoopDelay = 100; // milliseconds private readonly BufferBlock _queue; @@ -69,7 +69,7 @@ public ProjectManager( PackageDependencyChecker packageDependencyChecker, ProjectLoader projectLoader, OmniSharpWorkspace workspace, - CodeFixCacheForProjects codeFixesForProject, + CachingCodeFixProviderForProjects codeFixesForProject, RulesetsForProjects rulesetsForProjects, IAnalyzerAssemblyLoader assemblyLoader, ImmutableArray eventSinks) diff --git a/src/OmniSharp.MSBuild/ProjectSystem.cs b/src/OmniSharp.MSBuild/ProjectSystem.cs index a4b33f306e..15bd05d705 100644 --- a/src/OmniSharp.MSBuild/ProjectSystem.cs +++ b/src/OmniSharp.MSBuild/ProjectSystem.cs @@ -37,7 +37,7 @@ internal class ProjectSystem : IProjectSystem private readonly IFileSystemWatcher _fileSystemWatcher; private readonly FileSystemHelper _fileSystemHelper; private readonly ILoggerFactory _loggerFactory; - private readonly CodeFixCacheForProjects _codeFixesForProjects; + private readonly CachingCodeFixProviderForProjects _codeFixesForProjects; private readonly RulesetsForProjects _rulesetsForProjects; private readonly ILogger _logger; private readonly IAnalyzerAssemblyLoader _assemblyLoader; @@ -68,7 +68,7 @@ public ProjectSystem( IFileSystemWatcher fileSystemWatcher, FileSystemHelper fileSystemHelper, ILoggerFactory loggerFactory, - CodeFixCacheForProjects codeFixesForProjects, + CachingCodeFixProviderForProjects codeFixesForProjects, RulesetsForProjects rulesetsForProjects, IAnalyzerAssemblyLoader assemblyLoader, [ImportMany] IEnumerable eventSinks) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index cf54864392..6085733fa1 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -26,7 +26,7 @@ public abstract class BaseCodeActionService : IRequestHandl protected readonly IEnumerable Providers; protected readonly ILogger Logger; private readonly CSharpDiagnosticService analyzers; - private readonly CodeFixCacheForProjects codeFixesForProject; + private readonly CachingCodeFixProviderForProjects codeFixesForProject; private readonly MethodInfo _getNestedCodeActions; protected Lazy> OrderedCodeRefactoringProviders; @@ -37,7 +37,7 @@ public abstract class BaseCodeActionService : IRequestHandl { "CS8019", "RemoveUnnecessaryImportsFixable" } }; - protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, CSharpDiagnosticService analyzers, CodeFixCacheForProjects codeFixesForProject) + protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, CSharpDiagnosticService analyzers, CachingCodeFixProviderForProjects codeFixesForProject) { this.Workspace = workspace; this.Providers = providers; diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs index 4dc19bdd9a..c3516a3b93 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs @@ -11,22 +11,22 @@ namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 { [Shared] - [Export(typeof(CodeFixCacheForProjects))] - public class CodeFixCacheForProjects + [Export(typeof(CachingCodeFixProviderForProjects))] + public class CachingCodeFixProviderForProjects { - private readonly ConcurrentDictionary> _codeFixCache = new ConcurrentDictionary>(); + private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(); private readonly IAssemblyLoader _assemblyLoader; [ImportingConstructor] - public CodeFixCacheForProjects(IAssemblyLoader assemblyLoader) + public CachingCodeFixProviderForProjects(IAssemblyLoader assemblyLoader) { _assemblyLoader = assemblyLoader; } public IEnumerable GetAllCodeFixesForProject(string projectId) { - if (_codeFixCache.ContainsKey(projectId)) - return _codeFixCache[projectId]; + if (_cache.ContainsKey(projectId)) + return _cache[projectId]; return Enumerable.Empty(); } @@ -46,7 +46,7 @@ public void LoadFrom(string projectId, IEnumerable AnalyzerPaths) .Where(instance => instance != null); }); - _codeFixCache.AddOrUpdate(projectId, codeFixes, (_, __) => codeFixes); + _cache.AddOrUpdate(projectId, codeFixes, (_, __) => codeFixes); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs index a2b9d3ba6f..2bfaaefc24 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs @@ -22,7 +22,7 @@ public GetCodeActionsService( [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, CSharpDiagnosticService analyzers, - CodeFixCacheForProjects codeFixesForProjects) + CachingCodeFixProviderForProjects codeFixesForProjects) : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) { } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index 9fc92f4898..7a0a5cf4e8 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -41,7 +41,7 @@ public RunCodeActionService( [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, CSharpDiagnosticService analyzers, - CodeFixCacheForProjects codeFixesForProjects) + CachingCodeFixProviderForProjects codeFixesForProjects) : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) { _loader = loader; diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 298255e084..639084f55b 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -130,7 +130,7 @@ private IEnumerable WaitForPendingWorkIfNeededAndGetIt(IEnumerable LogTimeouts(task, nameof(_initializationQueueDoneSource))); + .ContinueWith(task => LogTimeouts(task, nameof(_initializationQueueDoneSource))); } private void LogTimeouts(Task task, string description) @@ -163,7 +163,7 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou var allAnalyzers = _providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) - .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzersForAllLanguages())); + .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language))); var compiled = await project.WithCompilationOptions( _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) From ec0e5206019ceca8acd7fb6df2656611a111b231 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 10 Sep 2018 22:43:05 +0300 Subject: [PATCH 071/178] Review fixes for code fix provider. --- src/OmniSharp.MSBuild/ProjectManager.cs | 2 +- .../Refactoring/V2/BaseCodeActionService.cs | 2 +- .../V2/CachingCodeFixProviderForProjects.cs | 55 +++++++++++++++++++ .../Refactoring/V2/CodeFixesForProjects.cs | 52 ------------------ 4 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs delete mode 100644 src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs diff --git a/src/OmniSharp.MSBuild/ProjectManager.cs b/src/OmniSharp.MSBuild/ProjectManager.cs index 355c640d1a..895b28ba7c 100644 --- a/src/OmniSharp.MSBuild/ProjectManager.cs +++ b/src/OmniSharp.MSBuild/ProjectManager.cs @@ -299,7 +299,7 @@ private void AddProject(ProjectFileInfo projectFileInfo) var projectInfo = projectFileInfo.CreateProjectInfo(_assemblyLoader); - _codeFixesForProject.LoadFrom(projectInfo.Id.ToString(), projectFileInfo.Analyzers); + _codeFixesForProject.LoadFrom(projectInfo); if(projectFileInfo.RuleSet != null) _rulesetsForProjects.AddOrUpdateRuleset(projectFileInfo.Id, projectFileInfo.RuleSet); diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 6085733fa1..c81eb391c3 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -146,7 +146,7 @@ private List GetSortedCodeFixProviders(Document document) { var providerList = this.Providers.SelectMany(provider => provider.CodeFixProviders) - .Concat(codeFixesForProject.GetAllCodeFixesForProject(document.Project.Id.ToString())); + .Concat(codeFixesForProject.GetAllCodeFixesForProject(document.Project.Id)); return ExtensionOrderer.GetOrderedOrUnorderedList(providerList, attribute => attribute.Name).ToList(); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs new file mode 100644 index 0000000000..a1ed4055b9 --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using OmniSharp.Services; +using OmniSharp.Utilities; + +namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 +{ + [Shared] + [Export(typeof(CachingCodeFixProviderForProjects))] + public class CachingCodeFixProviderForProjects + { + private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(); + + public IEnumerable GetAllCodeFixesForProject(ProjectId projectId) + { + if (_cache.ContainsKey(projectId)) + return _cache[projectId]; + return Enumerable.Empty(); + } + + public void LoadFrom(ProjectInfo project) + { + var codeFixes = project.AnalyzerReferences + .Where(x => x is AnalyzerFileReference) + .Cast() + .SelectMany(analyzerFileReference => { + return analyzerFileReference.GetAssembly().DefinedTypes + .Where(x => x.IsSubclassOf(typeof(CodeFixProvider))) + .Select(x => + { + var attribute = x.GetCustomAttribute(); + + if (attribute?.Languages != null && attribute.Languages.Contains(project.Language)) + { + return (CodeFixProvider)Activator.CreateInstance(x.AsType()); + } + + return null; + }) + .Where(x => x != null) + .ToImmutableArray(); + }); + + _cache.AddOrUpdate(project.Id, codeFixes, (_, __) => codeFixes); + } + } +} diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs deleted file mode 100644 index c3516a3b93..0000000000 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CodeFixesForProjects.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Composition; -using System.Linq; -using System.Reflection; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using OmniSharp.Services; -using OmniSharp.Utilities; - -namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 -{ - [Shared] - [Export(typeof(CachingCodeFixProviderForProjects))] - public class CachingCodeFixProviderForProjects - { - private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(); - private readonly IAssemblyLoader _assemblyLoader; - - [ImportingConstructor] - public CachingCodeFixProviderForProjects(IAssemblyLoader assemblyLoader) - { - _assemblyLoader = assemblyLoader; - } - - public IEnumerable GetAllCodeFixesForProject(string projectId) - { - if (_cache.ContainsKey(projectId)) - return _cache[projectId]; - return Enumerable.Empty(); - } - - public void LoadFrom(string projectId, IEnumerable AnalyzerPaths) - { - var codeFixes = AnalyzerPaths - .Where(x => x.EndsWith("CodeFixes.dll")) - .SelectMany(codeFixDllPath => - { - var loadedAssembly = _assemblyLoader.LoadFrom(codeFixDllPath); - var validTypes = loadedAssembly.GetTypes() - .Where(type => !type.GetTypeInfo().IsInterface && !type.GetTypeInfo().IsAbstract && !type.GetTypeInfo().ContainsGenericParameters) - .Where(t => typeof(CodeFixProvider).IsAssignableFrom(t)); - - return validTypes - .Select(type => type.CreateInstance()) - .Where(instance => instance != null); - }); - - _cache.AddOrUpdate(projectId, codeFixes, (_, __) => codeFixes); - } - } -} From 28a91182a9a7017167314508db77b9b0e1817ba3 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 10 Sep 2018 22:46:53 +0300 Subject: [PATCH 072/178] Small tweaks for linq. --- .../V2/CachingCodeFixProviderForProjects.cs | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs index a1ed4055b9..d97cfb77c2 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs @@ -29,25 +29,22 @@ public IEnumerable GetAllCodeFixesForProject(ProjectId projectI public void LoadFrom(ProjectInfo project) { var codeFixes = project.AnalyzerReferences - .Where(x => x is AnalyzerFileReference) - .Cast() - .SelectMany(analyzerFileReference => { - return analyzerFileReference.GetAssembly().DefinedTypes - .Where(x => x.IsSubclassOf(typeof(CodeFixProvider))) - .Select(x => - { - var attribute = x.GetCustomAttribute(); + .OfType() + .SelectMany(analyzerFileReference => analyzerFileReference.GetAssembly().DefinedTypes) + .Where(x => x.IsSubclassOf(typeof(CodeFixProvider))) + .Select(x => + { + var attribute = x.GetCustomAttribute(); - if (attribute?.Languages != null && attribute.Languages.Contains(project.Language)) - { - return (CodeFixProvider)Activator.CreateInstance(x.AsType()); - } + if (attribute?.Languages != null && attribute.Languages.Contains(project.Language)) + { + return (CodeFixProvider)Activator.CreateInstance(x.AsType()); + } - return null; - }) - .Where(x => x != null) - .ToImmutableArray(); - }); + return null; + }) + .Where(x => x != null) + .ToImmutableArray(); _cache.AddOrUpdate(project.Id, codeFixes, (_, __) => codeFixes); } From f32c8e7e09bb8c024fcaedc2d5381aab1da88173 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 11 Sep 2018 00:11:25 +0300 Subject: [PATCH 073/178] Immutablity to csharpdiagnostic service. --- .../Services/Diagnostics/CodeCheckService.cs | 8 ++- .../Diagnostics/DiagnosticsService.cs | 6 +- .../Refactoring/V2/BaseCodeActionService.cs | 2 +- .../Diagnostics/CSharpDiagnosticService.cs | 55 ++++++++++--------- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 893c333fa1..594aecec0b 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -1,4 +1,5 @@ -using System.Composition; +using System.Collections.Immutable; +using System.Composition; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -32,7 +33,10 @@ public async Task Handle(CodeCheckRequest request) : _workspace.CurrentSolution.Projects; var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult( - projectsForAnalysis.Where(x => x != null).Select(x => x.Id)); + projectsForAnalysis + .Where(x => x != null) + .Select(x => x.Id) + .ToImmutableArray()); var locations = analyzerResults .Where(x => (string.IsNullOrEmpty(request.FileName) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs index 74a7cf9b32..463542d883 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading.Tasks; @@ -33,7 +34,10 @@ public Task Handle(DiagnosticsRequest request) ? new[] { _workspace.GetDocument(request.FileName)?.Project } : _workspace.CurrentSolution.Projects; - _diagnostics.QueueForAnalysis(projectsForAnalysis.Where(x => x != null)); + _diagnostics.QueueForAnalysis(projectsForAnalysis + .Where(x => x != null) + .Select(x => x.Id) + .ToImmutableArray()); return Task.FromResult(new DiagnosticsResponse()); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index c81eb391c3..c562be9a91 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -104,7 +104,7 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText) private async Task CollectCodeFixesActions(Document document, TextSpan span, List codeActions) { - var analyzers = await this.analyzers.GetCurrentDiagnosticResult(new [] { document.Project.Id }); + var analyzers = await this.analyzers.GetCurrentDiagnosticResult(ImmutableArray.Create(document.Project.Id)); var groupedBySpan = analyzers.Select(x => x.diagnostic) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 639084f55b..e1bf2e8529 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -20,11 +20,11 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class CSharpDiagnosticService { private readonly ILogger _logger; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); - private readonly ConcurrentDictionary diagnostics)> _results = - new ConcurrentDictionary diagnostics)>(); - private readonly IEnumerable _providers; + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); + private readonly ConcurrentDictionary diagnostics)> _results = + new ConcurrentDictionary diagnostics)>(); + private readonly ImmutableArray _providers; private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; @@ -40,7 +40,7 @@ public CSharpDiagnosticService( RulesetsForProjects rulesetsForProjects) { _logger = loggerFactory.CreateLogger(); - _providers = providers; + _providers = providers.ToImmutableArray(); workspace.WorkspaceChanged += OnWorkspaceChanged; @@ -52,7 +52,7 @@ public CSharpDiagnosticService( { while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); - QueueForAnalysis(workspace.CurrentSolution.Projects); + QueueForAnalysis(workspace.CurrentSolution.Projects.Select(x => x.Id).ToImmutableArray()); _initializationQueueDoneSource.Cancel(); _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); }); @@ -77,7 +77,7 @@ private async Task Worker(CancellationToken token) } } - private IEnumerable<(Project project, CancellationTokenSource workReadySource)> GetThrottledWork() + private ImmutableArray<(Project project, CancellationTokenSource workReadySource)> GetThrottledWork() { lock (_workQueue) { @@ -85,18 +85,19 @@ private async Task Worker(CancellationToken token) .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. .Take(2) // Limit mount of work executed by once. This is needed on large solution... - .ToList(); + .Select(x => (project: _workspace.CurrentSolution.GetProject(x.Value.projectId), x.Value.workReadySource)) + .ToImmutableArray(); - foreach (var workKey in currentWork.Select(x => x.Key)) + foreach (var workKey in currentWork.Select(x => x.project.Id)) { _workQueue.TryRemove(workKey, out _); } - return currentWork.Select(x => (x.Value.project, x.Value.workReadySource)); + return currentWork; } } - public async Task> GetCurrentDiagnosticResult(IEnumerable projectIds) + public async Task> GetCurrentDiagnosticResult(ImmutableArray projectIds) { await WaitForInitialStartupWorkIfAny(); @@ -106,25 +107,27 @@ private async Task Worker(CancellationToken token) return _results .Where(x => projectIds.Any(pid => pid == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))); + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))) + .ToImmutableArray(); } - public void QueueForAnalysis(IEnumerable projects) + public void QueueForAnalysis(ImmutableArray projects) { - foreach (var project in projects) + foreach (var projectId in projects) { - _workQueue.AddOrUpdate(project.Id, - (modified: DateTime.UtcNow, project: project, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, project: project, oldValue.workReadySource)); + _workQueue.AddOrUpdate(projectId, + (modified: DateTime.UtcNow, projectId: projectId, new CancellationTokenSource()), + (_, oldValue) => (modified: DateTime.UtcNow, projectId: projectId, oldValue.workReadySource)); } } - private IEnumerable WaitForPendingWorkIfNeededAndGetIt(IEnumerable projectIds) + private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray projectIds) { return _workQueue .Where(x => projectIds.Any(pid => pid == x.Key)) .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) - .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))); + .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) + .ToImmutableArray(); } private Task WaitForInitialStartupWorkIfAny() @@ -144,8 +147,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded || changeEvent.Kind == WorkspaceChangeKind.ProjectAdded) { - var project = changeEvent.NewSolution.GetProject(changeEvent.ProjectId); - QueueForAnalysis(new[] { project }); + QueueForAnalysis(ImmutableArray.Create(changeEvent.ProjectId)); } } @@ -163,7 +165,8 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou var allAnalyzers = _providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) - .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language))); + .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language))) + .ToImmutableArray(); var compiled = await project.WithCompilationOptions( _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) @@ -171,10 +174,10 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou ImmutableArray results = ImmutableArray.Empty; - if (allAnalyzers.ToImmutableArray().Any()) + if (allAnalyzers.Any()) { results = await compiled - .WithAnalyzers(allAnalyzers.ToImmutableArray()) // This cannot be invoked with empty analyzers list. + .WithAnalyzers(allAnalyzers) // This cannot be invoked with empty analyzers list. .GetAllDiagnosticsAsync(token); } else @@ -216,7 +219,7 @@ private async Task AnalyzeSingleMiscFilesProject(Project project) .Select(x => x.GetDiagnostics()) .SelectMany(x => x); - _results[project.Id] = (project.Name, results); + _results[project.Id] = (project.Name, results.ToImmutableArray()); } } } From b33dd6020164dbb3a76c71056e3c319ff771fbd5 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 14 Sep 2018 18:06:09 +0300 Subject: [PATCH 074/178] Comment to explain non self descriptive initial wait. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index e1bf2e8529..6ada4732e9 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -130,6 +130,11 @@ private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray

Date: Sun, 16 Sep 2018 15:39:25 +0300 Subject: [PATCH 075/178] Support for roslyn 'IDE' analyzers. --- .../Diagnostics/CSharpDiagnosticService.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 6ada4732e9..d2dd70769f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -4,10 +4,12 @@ using System.Collections.Immutable; using System.Composition; using System.Linq; +using System.Reflection; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; using Microsoft.Extensions.Logging; using OmniSharp.Helpers; using OmniSharp.Models.Diagnostics; @@ -28,6 +30,12 @@ public class CSharpDiagnosticService private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; + + // This is workaround. + // Currently roslyn doesn't expose official way to use IDE analyzers during analysis. + // This options gives certain IDE analysis access for services that are not yet publicly available. + private readonly ConstructorInfo _workspaceAnalyzerOptionsConstructor; + private CancellationTokenSource _initializationQueueDoneSource = new CancellationTokenSource(); private int _throttlingMs = 500; @@ -48,6 +56,11 @@ public CSharpDiagnosticService( _workspace = workspace; _rulesetsForProjects = rulesetsForProjects; + _workspaceAnalyzerOptionsConstructor = Assembly + .Load("Microsoft.CodeAnalysis.Features") + .GetType("Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions") + .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution)}); + Task.Run(async () => { while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); @@ -181,8 +194,11 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou if (allAnalyzers.Any()) { + var workspaceAnalyzerOptions = + (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] {project.AnalyzerOptions, project.Solution.Options, project.Solution}); + results = await compiled - .WithAnalyzers(allAnalyzers) // This cannot be invoked with empty analyzers list. + .WithAnalyzers(allAnalyzers, workspaceAnalyzerOptions) // This cannot be invoked with empty analyzers list. .GetAllDiagnosticsAsync(token); } else From 862d8de36817ff3517536375957537f9490a9e33 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 23 Sep 2018 18:14:05 +0300 Subject: [PATCH 076/178] Testfixes and added test that IDE analyzers are enabled. --- .../DiagnosticsFacts.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs index d3c50ffe0b..20c50bc68e 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs @@ -25,7 +25,7 @@ public async Task CodeCheckSpecifiedFileOnly() var requestHandler = GetRequestHandler(SharedOmniSharpTestHost); var quickFixes = await requestHandler.Handle(new CodeCheckRequest() { FileName = "a.cs" }); - Assert.Single(quickFixes.QuickFixes); + Assert.Contains(quickFixes.QuickFixes.Select(x => x.ToString()), x => x.Contains("CS0029")); Assert.Equal("a.cs", quickFixes.QuickFixes.First().FileName); } @@ -39,7 +39,20 @@ public async Task CheckAllFiles() new TestFile("b.cs", "class C2 { int n = true; }")); var quickFixes = await handler.Handle(new CodeCheckRequest()); - Assert.Equal(2, quickFixes.QuickFixes.Count()); + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "a.cs"); + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "b.cs"); + } + + [Fact] + public async Task AnalysisSupportBuiltInIDEAnalysers() + { + var handler = GetRequestHandler(SharedOmniSharpTestHost); + + SharedOmniSharpTestHost.AddFilesToWorkspace( + new TestFile("a.cs", "class C1 { int n = true; }")); + + var quickFixes = await handler.Handle(new CodeCheckRequest()); + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("IDE0040")); } } } From f64738d7ae94527593746bb84775aba58d0fa87d Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 23 Sep 2018 18:29:47 +0300 Subject: [PATCH 077/178] Better comment on code based on review. --- .../Services/Refactoring/V2/BaseCodeActionService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index c562be9a91..af6837364b 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -31,7 +31,7 @@ public abstract class BaseCodeActionService : IRequestHandl protected Lazy> OrderedCodeRefactoringProviders; - // For some (probably visual studio specific?) reason diagnostic and fix codes doesn't match in every case. + // CS8019 isn't directly used (via roslyn) but has an analyzer that report different diagnostic based on CS8019 to improve user experience. private readonly Dictionary customDiagVsFixMap = new Dictionary { { "CS8019", "RemoveUnnecessaryImportsFixable" } From 9c25d6421fc68398249c64e3f5bfec1a66d1259f Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 23 Sep 2018 18:53:30 +0300 Subject: [PATCH 078/178] Testfix for signed project. --- .../test-projects/SolutionWithSignedProject/CallerLib/Caller.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-assets/test-projects/SolutionWithSignedProject/CallerLib/Caller.cs b/test-assets/test-projects/SolutionWithSignedProject/CallerLib/Caller.cs index 98d93786d6..fe77fc19df 100644 --- a/test-assets/test-projects/SolutionWithSignedProject/CallerLib/Caller.cs +++ b/test-assets/test-projects/SolutionWithSignedProject/CallerLib/Caller.cs @@ -4,7 +4,7 @@ public class Caller { public Caller() { - var callee = new Callee(); + Callee callee = new Callee(); } } } From 24f08b1ca6e76fea99a3cf49d1d98fab078cddfe Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 23 Sep 2018 19:37:19 +0300 Subject: [PATCH 079/178] Testfix. --- tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs index 02cfc810b5..7237c09ff2 100644 --- a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using OmniSharp.Cake.Services.RequestHandlers.Diagnostics; @@ -48,7 +49,10 @@ public async Task ShouldNotIncludeDiagnosticsFromLoadedFilesIfFileNameIsSpecifie var target = Argument(""target"", ""Default"");"; var diagnostics = await FindDiagnostics(input, includeFileName: true); - Assert.Empty(diagnostics.QuickFixes); + + // error.cake file contains code that cause error like: + // The type or namespace name 'asdf' could not be found (are you missing a using directive or an assembly reference?) (CS0246) + Assert.DoesNotContain(diagnostics.QuickFixes.Select(x => x.ToString()), x => x.Contains("CS0246")); } private async Task FindDiagnostics(string contents, bool includeFileName) From 1c3c98901290319ba4bb663f0ba67146e63f95c3 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 23 Sep 2018 21:00:20 +0300 Subject: [PATCH 080/178] Small test tweak. --- .../CustomRoslynAnalyzerFacts.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index fd7522024f..35b7362196 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -144,7 +144,7 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); + Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); } private static Dictionary CreateRules(TestAnalyzerReference testAnalyzerRef, ReportDiagnostic diagnostic) @@ -177,7 +177,7 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.DoesNotContain(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + Assert.DoesNotContain(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } [Fact] @@ -201,7 +201,7 @@ public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enab var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } private IEnumerable CreateProjectWitFile(TestFile testFile, TestAnalyzerReference testAnalyzerRef = null) From a0f52586f5bf78565f28c7460dbf2679ef38e3dc Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 23 Sep 2018 21:12:21 +0300 Subject: [PATCH 081/178] Buildfix. --- .../OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index 35b7362196..a5076fd347 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -144,7 +144,7 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); + Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); } private static Dictionary CreateRules(TestAnalyzerReference testAnalyzerRef, ReportDiagnostic diagnostic) From bf154f92f2b471cdecb42aa005f0269062d74722 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 08:11:09 +0300 Subject: [PATCH 082/178] Tweaks for issue where workspace updates comes after request. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index d2dd70769f..a579b59a89 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -37,7 +37,7 @@ public class CSharpDiagnosticService private readonly ConstructorInfo _workspaceAnalyzerOptionsConstructor; private CancellationTokenSource _initializationQueueDoneSource = new CancellationTokenSource(); - private int _throttlingMs = 500; + private readonly int _throttlingMs = 300; [ImportingConstructor] public CSharpDiagnosticService( @@ -140,6 +140,7 @@ private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray

projectIds.Any(pid => pid == x.Key)) .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) + .Concat(new [] { Task.Delay(250)}) // Workaround for issue where information about updates from workspace are not at sync with calls. .ToImmutableArray(); } @@ -162,6 +163,7 @@ private void LogTimeouts(Task task, string description) private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged + || changeEvent.Kind == WorkspaceChangeKind.DocumentRemoved || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded || changeEvent.Kind == WorkspaceChangeKind.ProjectAdded) { From 2e10c358ee8324db7931f36cc6381b8575830fab Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 08:25:47 +0300 Subject: [PATCH 083/178] Added assert to protect possibly internal changes of roslyn. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index a579b59a89..ababf4c455 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -59,7 +59,8 @@ public CSharpDiagnosticService( _workspaceAnalyzerOptionsConstructor = Assembly .Load("Microsoft.CodeAnalysis.Features") .GetType("Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions") - .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution)}); + .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution)}) + ?? throw new InvalidOperationException("Could not resolve 'Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions' for IDE analyzers."); Task.Run(async () => { From 9028a0184caf387926b43f1982b6dd5fdb2bcbca Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 08:35:36 +0300 Subject: [PATCH 084/178] Increased timeout and added comment about asserting. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index ababf4c455..4a24134e79 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -139,7 +139,7 @@ private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray

projectIds.Any(pid => pid == x.Key)) - .Select(x => Task.Delay(10 * 1000, x.Value.workReadySource.Token) + .Select(x => Task.Delay(30 * 1000, x.Value.workReadySource.Token) .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) .Concat(new [] { Task.Delay(250)}) // Workaround for issue where information about updates from workspace are not at sync with calls. .ToImmutableArray(); @@ -156,6 +156,8 @@ private Task WaitForInitialStartupWorkIfAny() .ContinueWith(task => LogTimeouts(task, nameof(_initializationQueueDoneSource))); } + // This is basically asserting mechanism for hanging analysis if any. If this doesn't exist tracking + // down why results doesn't come up (for example in situation when theres bad analyzer that takes ages to complete). private void LogTimeouts(Task task, string description) { if (!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); From 41ccefcc268caee5c54b22eca49e0b2a2c376242 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 09:02:42 +0300 Subject: [PATCH 085/178] Further tweaked timeouts on event mechanism asserts. --- .../DiagnosticsV2Facts.DiagnosticTestEmitter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs index 8945518f14..52ff79f5c6 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs @@ -27,7 +27,7 @@ public async Task ExpectForEmitted(Expression> pred return; } - await Task.Delay(50); + await Task.Delay(250); } throw new InvalidOperationException($"Timeout reached before expected event count reached before prediction {predicate} came true, current diagnostics '{String.Join(";", Messages.SelectMany(x => x.Results))}'"); From 164bae4bd5847bc3ed3d3012b23a3e3af968b496 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 09:32:02 +0300 Subject: [PATCH 086/178] Increased another 'assert' timeout. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 4a24134e79..1588f7d3c2 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -152,7 +152,7 @@ private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray

LogTimeouts(task, nameof(_initializationQueueDoneSource))); } From 02ca6cbdff1c30060b4d92a001c551a542949474 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 11:15:22 +0300 Subject: [PATCH 087/178] Fix for threading issue in test utility. --- .../DiagnosticsV2Facts.DiagnosticTestEmitter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs index 52ff79f5c6..5136f0adbf 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.DiagnosticTestEmitter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; @@ -12,7 +13,8 @@ public partial class DiagnosticsV2Facts { private class DiagnosticTestEmitter : IEventEmitter { - public readonly List Messages = new List(); + public readonly ConcurrentBag Messages = new ConcurrentBag(); + private readonly TaskCompletionSource _tcs; public async Task ExpectForEmitted(Expression> predicate) From 39e3bdc65ac4040b490348d8ac301e64c78926fe Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 11:48:09 +0300 Subject: [PATCH 088/178] Trigger to test build stability --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 1588f7d3c2..2d63f23c67 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -149,7 +149,7 @@ private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray

Date: Fri, 5 Oct 2018 11:50:16 +0300 Subject: [PATCH 089/178] Trigger to test build stability. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 2d63f23c67..b39b8ecf0e 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -149,7 +149,7 @@ private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray

Date: Fri, 5 Oct 2018 11:50:49 +0300 Subject: [PATCH 090/178] Trigger to test build stability. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index b39b8ecf0e..1588f7d3c2 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -149,7 +149,7 @@ private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray

Date: Fri, 5 Oct 2018 12:42:24 +0300 Subject: [PATCH 091/178] Yet another attempt to get build execute. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 1588f7d3c2..9be6322476 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -98,7 +98,7 @@ private async Task Worker(CancellationToken token) var currentWork = _workQueue .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. - .Take(2) // Limit mount of work executed by once. This is needed on large solution... + .Take(2) // Limit mount of work executed by once. This is needed on large solution... .Select(x => (project: _workspace.CurrentSolution.GetProject(x.Value.projectId), x.Value.workReadySource)) .ToImmutableArray(); From f8cce2d9b34250caf0e40c299a5b11ca3d3c071b Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 14:39:23 +0300 Subject: [PATCH 092/178] Trigger build From dbab30c18502ee84ff066cbe03467eb7514fecd0 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 5 Oct 2018 15:53:31 +0300 Subject: [PATCH 093/178] Added original caller name tracking for OmnisharpTestHostDisposing. --- .../Services/Diagnostics/CodeCheckService.cs | 4 ++-- tests/TestUtility/OmniSharpTestHost.cs | 18 ++++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 594aecec0b..2a99e347df 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -34,8 +34,8 @@ public async Task Handle(CodeCheckRequest request) var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult( projectsForAnalysis - .Where(x => x != null) - .Select(x => x.Id) + .Where(project => project != null) + .Select(project => project.Id) .ToImmutableArray()); var locations = analyzerResults diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index e20be7cfa0..5cf9c2dc9b 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -4,6 +4,7 @@ using System.Composition.Hosting.Core; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.Extensions.DependencyInjection; @@ -42,6 +43,7 @@ public class OmniSharpTestHost : DisposableObject private readonly CompositionHost _compositionHost; private Dictionary<(string name, string language), Lazy> _handlers; + private readonly string _originalCreatorToTrackDownMissedDisposes; public OmniSharpWorkspace Workspace { get; } public ILoggerFactory LoggerFactory { get; } @@ -49,7 +51,8 @@ public class OmniSharpTestHost : DisposableObject private OmniSharpTestHost( IServiceProvider serviceProvider, - CompositionHost compositionHost) + CompositionHost compositionHost, + string originalCreatorToTrackDownMissedDisposes) { _serviceProvider = serviceProvider; _compositionHost = compositionHost; @@ -57,11 +60,12 @@ private OmniSharpTestHost( this.Workspace = compositionHost.GetExport(); this.LoggerFactory = _serviceProvider.GetRequiredService(); this.Logger = this.LoggerFactory.CreateLogger(); + _originalCreatorToTrackDownMissedDisposes = originalCreatorToTrackDownMissedDisposes; } ~OmniSharpTestHost() { - throw new InvalidOperationException($"{nameof(OmniSharpTestHost)}.{nameof(Dispose)}() not called."); + throw new InvalidOperationException($"{nameof(OmniSharpTestHost)}.{nameof(Dispose)}() not called, creation of object originated from {_originalCreatorToTrackDownMissedDisposes}."); } protected override void DisposeCore(bool disposing) @@ -75,14 +79,15 @@ protected override void DisposeCore(bool disposing) public static OmniSharpTestHost Create( IServiceProvider serviceProvider, - IEnumerable additionalExports = null) + IEnumerable additionalExports = null, + [CallerMemberName] string callerName = "") { var compositionHost = new CompositionHostBuilder(serviceProvider, s_lazyAssemblies.Value, additionalExports) .Build(); WorkspaceInitializer.Initialize(serviceProvider, compositionHost); - var host = new OmniSharpTestHost(serviceProvider, compositionHost); + var host = new OmniSharpTestHost(serviceProvider, compositionHost, callerName); // Force workspace to be updated var service = host.GetWorkspaceInformationService(); @@ -96,12 +101,13 @@ public static OmniSharpTestHost Create( ITestOutputHelper testOutput = null, IEnumerable> configurationData = null, DotNetCliVersion dotNetCliVersion = DotNetCliVersion.Current, - IEnumerable additionalExports = null) + IEnumerable additionalExports = null, + [CallerMemberName] string callerName = "") { var environment = new OmniSharpEnvironment(path, logLevel: LogLevel.Trace); var serviceProvider = TestServiceProvider.Create(testOutput, environment, configurationData, dotNetCliVersion); - return Create(serviceProvider, additionalExports); + return Create(serviceProvider, additionalExports, callerName); } public T GetExport() From be8f23e3548fb8efb02d093e5d139f2d4076e93a Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Oct 2018 15:48:52 +0300 Subject: [PATCH 094/178] Attempt to improve robustness of tests. --- .../CustomRoslynAnalyzerFacts.cs | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index a5076fd347..f6f444d4a1 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -11,6 +12,7 @@ using TestUtility; using Xunit; using Xunit.Abstractions; +using Xunit.Sdk; namespace OmniSharp.Roslyn.CSharp.Tests { @@ -106,9 +108,11 @@ public async Task When_custom_analyzers_are_executed_then_return_results() var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); - var result = await codeCheckService.Handle(new CodeCheckRequest()); - - Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + await RetryOn(async () => + { + var result = await codeCheckService.Handle(new CodeCheckRequest()); + Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + }); } [Fact] @@ -119,9 +123,11 @@ public async Task Always_return_results_from_net_default_analyzers() CreateProjectWitFile(testFile); - var result = await codeCheckService.Handle(new CodeCheckRequest()); - - Assert.Contains(result.QuickFixes.Where(x => x.FileName == testFile.FileName), f => f.Text.Contains("CS")); + await RetryOn(async () => + { + var result = await codeCheckService.Handle(new CodeCheckRequest()); + Assert.Contains(result.QuickFixes.Where(x => x.FileName == testFile.FileName), f => f.Text.Contains("CS")); + }); } [Fact] @@ -142,9 +148,11 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ testRules.ToImmutableDictionary(), new ImmutableArray())); - var result = await codeCheckService.Handle(new CodeCheckRequest()); - - Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); + await RetryOn(async () => + { + var result = await codeCheckService.Handle(new CodeCheckRequest()); + Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); + }); } private static Dictionary CreateRules(TestAnalyzerReference testAnalyzerRef, ReportDiagnostic diagnostic) @@ -176,7 +184,6 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() new ImmutableArray())); var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.DoesNotContain(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } @@ -199,14 +206,40 @@ public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enab testRules.ToImmutableDictionary(), new ImmutableArray())); - var result = await codeCheckService.Handle(new CodeCheckRequest()); + await RetryOn(async () => + { + var result = await codeCheckService.Handle(new CodeCheckRequest()); + Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + }); + } - Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + // On 99% cases asserts should not require retry, however build systems are very slow sometimes with unpredictable ways. + // This should be replaced with emitted events listener aproach after LSP is mainstream. + private static Task RetryOn(Func action, int maxAttemptCount = 5) where TException : Exception + { + var exceptions = new List(); + + for (int attempted = 0; attempted < maxAttemptCount; attempted++) + { + try + { + if (attempted > 0) + { + Thread.Sleep(TimeSpan.FromSeconds(10)); + } + return action(); + } + catch (TException ex) + { + exceptions.Add(ex); + } + } + throw new AggregateException(exceptions); } private IEnumerable CreateProjectWitFile(TestFile testFile, TestAnalyzerReference testAnalyzerRef = null) { - var analyzerReferences = testAnalyzerRef == null ? default: + var analyzerReferences = testAnalyzerRef == null ? default : new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray(); return TestHelpers.AddProjectToWorkspace( From 9cca4244bb9349bbc4e97217f553fae8a87e71ef Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Oct 2018 16:00:50 +0300 Subject: [PATCH 095/178] Moved tool to test utilities. --- tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs | 9 +++-- .../CustomRoslynAnalyzerFacts.cs | 36 +++++-------------- 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs index 7237c09ff2..fa21df66ed 100644 --- a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs @@ -9,6 +9,7 @@ using TestUtility; using Xunit; using Xunit.Abstractions; +using Xunit.Sdk; namespace OmniSharp.Cake.Tests { @@ -28,8 +29,12 @@ public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() { const string input = @"zzz"; - var diagnostics = await FindDiagnostics(input, includeFileName: true); - Assert.NotEmpty(diagnostics.QuickFixes); + // Remove this retry once rare issue with missing update is handled or system is replaced with LSP event + // based approach. + await RetryAssert.On(async () => { + var diagnostics = await FindDiagnostics(input, includeFileName: true); + Assert.NotEmpty(diagnostics.QuickFixes); + }); } [Fact] diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index f6f444d4a1..806446388a 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -108,7 +108,11 @@ public async Task When_custom_analyzers_are_executed_then_return_results() var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); - await RetryOn(async () => + // This retry should be replaced with emitted events listener aproach after LSP is mainstream. + // Retry is required when build system greatly slows down some times, another issue is that + // it feels that some of update events from workspace get lost and it requires further investigation. + // If missing events are true then same issue will happen with LSP version too. + await RetryAssert.On(async () => { var result = await codeCheckService.Handle(new CodeCheckRequest()); Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); @@ -123,7 +127,7 @@ public async Task Always_return_results_from_net_default_analyzers() CreateProjectWitFile(testFile); - await RetryOn(async () => + await RetryAssert.On(async () => { var result = await codeCheckService.Handle(new CodeCheckRequest()); Assert.Contains(result.QuickFixes.Where(x => x.FileName == testFile.FileName), f => f.Text.Contains("CS")); @@ -148,7 +152,7 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ testRules.ToImmutableDictionary(), new ImmutableArray())); - await RetryOn(async () => + await RetryAssert.On(async () => { var result = await codeCheckService.Handle(new CodeCheckRequest()); Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); @@ -206,37 +210,13 @@ public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enab testRules.ToImmutableDictionary(), new ImmutableArray())); - await RetryOn(async () => + await RetryAssert.On(async () => { var result = await codeCheckService.Handle(new CodeCheckRequest()); Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); }); } - // On 99% cases asserts should not require retry, however build systems are very slow sometimes with unpredictable ways. - // This should be replaced with emitted events listener aproach after LSP is mainstream. - private static Task RetryOn(Func action, int maxAttemptCount = 5) where TException : Exception - { - var exceptions = new List(); - - for (int attempted = 0; attempted < maxAttemptCount; attempted++) - { - try - { - if (attempted > 0) - { - Thread.Sleep(TimeSpan.FromSeconds(10)); - } - return action(); - } - catch (TException ex) - { - exceptions.Add(ex); - } - } - throw new AggregateException(exceptions); - } - private IEnumerable CreateProjectWitFile(TestFile testFile, TestAnalyzerReference testAnalyzerRef = null) { var analyzerReferences = testAnalyzerRef == null ? default : From 9f649356d44c3e4659bc71aa9fdeee154c15f090 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Oct 2018 16:07:32 +0300 Subject: [PATCH 096/178] Added missing file. --- tests/TestUtility/RetryAssert.cs | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/TestUtility/RetryAssert.cs diff --git a/tests/TestUtility/RetryAssert.cs b/tests/TestUtility/RetryAssert.cs new file mode 100644 index 0000000000..0960c4ad1c --- /dev/null +++ b/tests/TestUtility/RetryAssert.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace TestUtility +{ + public static class RetryAssert + { + // On 99% cases asserts should not require retry, however build systems are very slow sometimes with unpredictable ways. + public static Task On(Func action, int maxAttemptCount = 5) where TException : Exception + { + var exceptions = new List(); + + for (int attempted = 0; attempted < maxAttemptCount; attempted++) + { + try + { + if (attempted > 0) + { + Thread.Sleep(TimeSpan.FromSeconds(10)); + } + return action(); + } + catch (TException ex) + { + exceptions.Add(ex); + } + } + throw new AggregateException(exceptions); + } + } +} \ No newline at end of file From ffe8e8ad318e1c3152541958632fb68e4119fecc Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Oct 2018 16:32:03 +0300 Subject: [PATCH 097/178] Test build robustnes From ed82a59cfc8dd9cc604276b919dd4e0051864950 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 7 Oct 2018 17:52:01 +0300 Subject: [PATCH 098/178] Test build robustnes From c322d463312125c5ef75a69c8850c74be05c40e1 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 2 Nov 2018 08:15:09 +0200 Subject: [PATCH 099/178] Fix for possible null ref issue. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 9be6322476..800a6093b4 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -98,16 +98,18 @@ private async Task Worker(CancellationToken token) var currentWork = _workQueue .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. - .Take(2) // Limit mount of work executed by once. This is needed on large solution... - .Select(x => (project: _workspace.CurrentSolution.GetProject(x.Value.projectId), x.Value.workReadySource)) + .Take(2) // Limit mount of work executed by once. This is needed on large solution... .ToImmutableArray(); - foreach (var workKey in currentWork.Select(x => x.project.Id)) + foreach (var workKey in currentWork.Select(x => x.Key)) { _workQueue.TryRemove(workKey, out _); } - return currentWork; + return currentWork + .Select(x => (project: _workspace?.CurrentSolution?.GetProject(x.Value.projectId), x.Value.workReadySource)) + .Where(x => x.project != null) // This may occur if project removed middle of analysis. + .ToImmutableArray(); } } From 336a7996170e84e245b7c20041c54ec4e4cd2554 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 2 Nov 2018 08:22:34 +0200 Subject: [PATCH 100/178] Fixed mistake on exception handling when using WhenAll. --- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 800a6093b4..aef704f6ee 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -217,6 +217,10 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou EmitDiagnostics(results); } + catch(Exception ex) + { + _logger.LogError($"Analysis of project {project.Id} ({project.Name}) failed, underlaying error: {ex}"); + } finally { workReadySource.Cancel(); From acb954f2f053992387b41a3dcdd84aff44de7325 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 2 Nov 2018 10:08:45 +0200 Subject: [PATCH 101/178] Trigger build From c33d8f3c51ebe944e8e68cf35ea40528e3ffe6f0 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 09:25:19 +0200 Subject: [PATCH 102/178] Added fallback code to CodeCheckService, next need to figure out how to configure it. --- .../Services/Diagnostics/CodeCheckService.cs | 58 ++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 2a99e347df..51d93c3d28 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -1,4 +1,5 @@ -using System.Collections.Immutable; +using System.Collections.Generic; +using System.Collections.Immutable; using System.Composition; using System.Linq; using System.Threading.Tasks; @@ -8,6 +9,7 @@ using OmniSharp.Mef; using OmniSharp.Models; using OmniSharp.Models.CodeCheck; +using OmniSharp.Models.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { @@ -28,6 +30,16 @@ public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticService ro public async Task Handle(CodeCheckRequest request) { + // if(true) + // { + // var documents = request.FileName != null + // ? _workspace.GetDocuments(request.FileName) + // : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); + + // var quickFixes = await FindDiagnosticLocationsAsync(documents); + // return new QuickFixResponse(quickFixes); + // } + var projectsForAnalysis = !string.IsNullOrEmpty(request.FileName) ? new[] { _workspace.GetDocument(request.FileName)?.Project } : _workspace.CurrentSolution.Projects; @@ -46,5 +58,49 @@ public async Task Handle(CodeCheckRequest request) return new QuickFixResponse( locations.Where(x => x.FileName != null)); } + + private static DiagnosticLocation ToDiagnosticLocation(Diagnostic diagnostic) + { + var span = diagnostic.Location.GetMappedLineSpan(); + return new DiagnosticLocation + { + FileName = span.Path, + Line = span.StartLinePosition.Line, + Column = span.StartLinePosition.Character, + EndLine = span.EndLinePosition.Line, + EndColumn = span.EndLinePosition.Character, + Text = diagnostic.GetMessage(), + LogLevel = diagnostic.Severity.ToString(), + Id = diagnostic.Id + }; + } + + private static async Task> FindDiagnosticLocationsAsync(IEnumerable documents) + { + if (documents == null || !documents.Any()) return Enumerable.Empty(); + + var items = new List(); + foreach (var document in documents) + { + var semanticModel = await document.GetSemanticModelAsync(); + IEnumerable diagnostics = semanticModel.GetDiagnostics(); + + foreach (var quickFix in diagnostics.Select(d => ToDiagnosticLocation(d))) + { + var existingQuickFix = items.FirstOrDefault(q => q.Equals(quickFix)); + if (existingQuickFix == null) + { + quickFix.Projects.Add(document.Project.Name); + items.Add(quickFix); + } + else + { + existingQuickFix.Projects.Add(document.Project.Name); + } + } + } + + return items; + } } } From c34f5543ee9243567e3e903e10b8307eac8f75be Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 09:30:16 +0200 Subject: [PATCH 103/178] Removed duplicate code. --- .../Services/Diagnostics/CodeCheckService.cs | 18 +----------------- .../Diagnostics/CSharpDiagnosticService.cs | 3 ++- 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 51d93c3d28..445a318f66 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -59,22 +59,6 @@ public async Task Handle(CodeCheckRequest request) locations.Where(x => x.FileName != null)); } - private static DiagnosticLocation ToDiagnosticLocation(Diagnostic diagnostic) - { - var span = diagnostic.Location.GetMappedLineSpan(); - return new DiagnosticLocation - { - FileName = span.Path, - Line = span.StartLinePosition.Line, - Column = span.StartLinePosition.Character, - EndLine = span.EndLinePosition.Line, - EndColumn = span.EndLinePosition.Character, - Text = diagnostic.GetMessage(), - LogLevel = diagnostic.Severity.ToString(), - Id = diagnostic.Id - }; - } - private static async Task> FindDiagnosticLocationsAsync(IEnumerable documents) { if (documents == null || !documents.Any()) return Enumerable.Empty(); @@ -85,7 +69,7 @@ private static async Task> FindDiagnosticLocatio var semanticModel = await document.GetSemanticModelAsync(); IEnumerable diagnostics = semanticModel.GetDiagnostics(); - foreach (var quickFix in diagnostics.Select(d => ToDiagnosticLocation(d))) + foreach (var quickFix in diagnostics.Select(d => d.ToDiagnosticLocation())) { var existingQuickFix = items.FirstOrDefault(q => q.Equals(quickFix)); if (existingQuickFix == null) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index aef704f6ee..0de0684d48 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -50,7 +50,6 @@ public CSharpDiagnosticService( _logger = loggerFactory.CreateLogger(); _providers = providers.ToImmutableArray(); - workspace.WorkspaceChanged += OnWorkspaceChanged; _forwarder = forwarder; _workspace = workspace; @@ -62,6 +61,8 @@ public CSharpDiagnosticService( .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution)}) ?? throw new InvalidOperationException("Could not resolve 'Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions' for IDE analyzers."); + _workspace.WorkspaceChanged += OnWorkspaceChanged; + Task.Run(async () => { while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); From c9548f690cd21c160dd7dc8ff1003c53ddf44614 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 09:51:23 +0200 Subject: [PATCH 104/178] Added flag to options. --- .../Options/RoslynExtensionsOptions.cs | 2 ++ .../Services/Diagnostics/CodeCheckService.cs | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs b/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs index 0072724225..b94f574965 100644 --- a/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs +++ b/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs @@ -7,6 +7,8 @@ namespace OmniSharp.Options { public class RoslynExtensionsOptions { + public bool EnableExpiremantalCodeAnalysis { get; set; } + public string[] LocationPaths { get; set; } public IEnumerable GetNormalizedLocationPaths(IOmniSharpEnvironment env) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 445a318f66..bdb4e70fb3 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -10,6 +10,7 @@ using OmniSharp.Models; using OmniSharp.Models.CodeCheck; using OmniSharp.Models.Diagnostics; +using OmniSharp.Options; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { @@ -18,27 +19,29 @@ public class CodeCheckService : IRequestHandler _logger; [ImportingConstructor] - public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticService roslynAnalyzer, ILoggerFactory loggerFactory) + public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticService roslynAnalyzer, ILoggerFactory loggerFactory, OmniSharpOptions options) { _workspace = workspace; _roslynAnalyzer = roslynAnalyzer; + _options = options; _logger = loggerFactory.CreateLogger(); } public async Task Handle(CodeCheckRequest request) { - // if(true) - // { - // var documents = request.FileName != null - // ? _workspace.GetDocuments(request.FileName) - // : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); + if(!_options.RoslynExtensionsOptions.EnableExpiremantalCodeAnalysis) + { + var documents = request.FileName != null + ? _workspace.GetDocuments(request.FileName) + : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); - // var quickFixes = await FindDiagnosticLocationsAsync(documents); - // return new QuickFixResponse(quickFixes); - // } + var quickFixes = await FindDiagnosticLocationsAsync(documents); + return new QuickFixResponse(quickFixes); + } var projectsForAnalysis = !string.IsNullOrEmpty(request.FileName) ? new[] { _workspace.GetDocument(request.FileName)?.Project } From 42a939acc7c255dd1986bf27b2b018fea0754343 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 10:26:50 +0200 Subject: [PATCH 105/178] Use expiremental analysis as default during testing. --- tests/TestUtility/OmniSharpTestHost.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 5cf9c2dc9b..90d8cb324b 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -105,6 +105,13 @@ public static OmniSharpTestHost Create( [CallerMemberName] string callerName = "") { var environment = new OmniSharpEnvironment(path, logLevel: LogLevel.Trace); + + // During testing new analysis service is used as default. + if(configurationData == null) + { + configurationData = new [] { new KeyValuePair("RoslynExtensionsOptions:EnableExpiremantalCodeAnalysis", "true") }; + } + var serviceProvider = TestServiceProvider.Create(testOutput, environment, configurationData, dotNetCliVersion); return Create(serviceProvider, additionalExports, callerName); From 7c7d85a02be30924b7eeab58773d2703da062ffa Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 10:52:55 +0200 Subject: [PATCH 106/178] Disabled CsharpDiagnosticService workers if not enabled. --- .../Diagnostics/CSharpDiagnosticService.cs | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 0de0684d48..10fcc78251 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging; using OmniSharp.Helpers; using OmniSharp.Models.Diagnostics; +using OmniSharp.Options; using OmniSharp.Services; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics @@ -45,7 +46,8 @@ public CSharpDiagnosticService( [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, DiagnosticEventForwarder forwarder, - RulesetsForProjects rulesetsForProjects) + RulesetsForProjects rulesetsForProjects, + OmniSharpOptions options) { _logger = loggerFactory.CreateLogger(); _providers = providers.ToImmutableArray(); @@ -58,21 +60,24 @@ public CSharpDiagnosticService( _workspaceAnalyzerOptionsConstructor = Assembly .Load("Microsoft.CodeAnalysis.Features") .GetType("Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions") - .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution)}) + .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution) }) ?? throw new InvalidOperationException("Could not resolve 'Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions' for IDE analyzers."); - _workspace.WorkspaceChanged += OnWorkspaceChanged; - - Task.Run(async () => + if (options.RoslynExtensionsOptions.EnableExpiremantalCodeAnalysis) { - while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); + _workspace.WorkspaceChanged += OnWorkspaceChanged; - QueueForAnalysis(workspace.CurrentSolution.Projects.Select(x => x.Id).ToImmutableArray()); - _initializationQueueDoneSource.Cancel(); - _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); - }); + Task.Run(async () => + { + while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); - Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); + QueueForAnalysis(workspace.CurrentSolution.Projects.Select(x => x.Id).ToImmutableArray()); + _initializationQueueDoneSource.Cancel(); + _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); + }); + + Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); + } } private async Task Worker(CancellationToken token) @@ -144,7 +149,7 @@ private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray

projectIds.Any(pid => pid == x.Key)) .Select(x => Task.Delay(30 * 1000, x.Value.workReadySource.Token) .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) - .Concat(new [] { Task.Delay(250)}) // Workaround for issue where information about updates from workspace are not at sync with calls. + .Concat(new[] { Task.Delay(250) }) // Workaround for issue where information about updates from workspace are not at sync with calls. .ToImmutableArray(); } @@ -203,7 +208,7 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou if (allAnalyzers.Any()) { var workspaceAnalyzerOptions = - (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] {project.AnalyzerOptions, project.Solution.Options, project.Solution}); + (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution.Options, project.Solution }); results = await compiled .WithAnalyzers(allAnalyzers, workspaceAnalyzerOptions) // This cannot be invoked with empty analyzers list. @@ -218,7 +223,7 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou EmitDiagnostics(results); } - catch(Exception ex) + catch (Exception ex) { _logger.LogError($"Analysis of project {project.Id} ({project.Name}) failed, underlaying error: {ex}"); } From 3cab5653f34ddcf1020de878b1ed3a612286a539 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 11:17:51 +0200 Subject: [PATCH 107/178] Buildfix. --- tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index 722296a615..e0631b028a 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Threading.Tasks; using OmniSharp.Models.Diagnostics; +using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Services; using TestUtility; @@ -43,7 +44,9 @@ public async Task CodeCheckSpecifiedFileOnly(string filename) private CSharpDiagnosticService CreateDiagnosticService(DiagnosticEventForwarder forwarder) { - return new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects()); + var options = new OmniSharpOptions(); + options.RoslynExtensionsOptions.EnableExpiremantalCodeAnalysis = true; + return new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects(), options); } [Theory] From 8b538a22493f040a8947938dfeb7ccd569b72361 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 15:24:44 +0200 Subject: [PATCH 108/178] Fix for codefix support when analyzers are disabled. --- .../Helpers/DiagnosticExtensions.cs | 28 +++++++++++++++++ .../Services/Diagnostics/CodeCheckService.cs | 30 +------------------ .../Refactoring/V2/BaseCodeActionService.cs | 25 ++++++++++++---- .../Refactoring/V2/GetCodeActionsService.cs | 6 ++-- .../Refactoring/V2/RunCodeActionService.cs | 6 ++-- 5 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs index 14cbd48c05..a1ae8d23b3 100644 --- a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs +++ b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs @@ -40,5 +40,33 @@ internal static IEnumerable DistinctDiagnosticLocationsByPro return location; }); } + + internal static async Task> FindDiagnosticLocationsAsync(this IEnumerable documents) + { + if (documents == null || !documents.Any()) return Enumerable.Empty(); + + var items = new List(); + foreach (var document in documents) + { + var semanticModel = await document.GetSemanticModelAsync(); + IEnumerable diagnostics = semanticModel.GetDiagnostics(); + + foreach (var quickFix in diagnostics.Select(d => d.ToDiagnosticLocation())) + { + var existingQuickFix = items.FirstOrDefault(q => q.Equals(quickFix)); + if (existingQuickFix == null) + { + quickFix.Projects.Add(document.Project.Name); + items.Add(quickFix); + } + else + { + existingQuickFix.Projects.Add(document.Project.Name); + } + } + } + + return items; + } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index bdb4e70fb3..7027cb8712 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -39,7 +39,7 @@ public async Task Handle(CodeCheckRequest request) ? _workspace.GetDocuments(request.FileName) : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); - var quickFixes = await FindDiagnosticLocationsAsync(documents); + var quickFixes = await documents.FindDiagnosticLocationsAsync(); return new QuickFixResponse(quickFixes); } @@ -61,33 +61,5 @@ public async Task Handle(CodeCheckRequest request) return new QuickFixResponse( locations.Where(x => x.FileName != null)); } - - private static async Task> FindDiagnosticLocationsAsync(IEnumerable documents) - { - if (documents == null || !documents.Any()) return Enumerable.Empty(); - - var items = new List(); - foreach (var document in documents) - { - var semanticModel = await document.GetSemanticModelAsync(); - IEnumerable diagnostics = semanticModel.GetDiagnostics(); - - foreach (var quickFix in diagnostics.Select(d => d.ToDiagnosticLocation())) - { - var existingQuickFix = items.FirstOrDefault(q => q.Equals(quickFix)); - if (existingQuickFix == null) - { - quickFix.Projects.Add(document.Project.Name); - items.Add(quickFix); - } - else - { - existingQuickFix.Projects.Add(document.Project.Name); - } - } - } - - return items; - } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index af6837364b..014c4d4950 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -12,8 +12,10 @@ using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; using OmniSharp.Extensions; +using OmniSharp.Helpers; using OmniSharp.Mef; using OmniSharp.Models.V2.CodeActions; +using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Services; using OmniSharp.Utilities; @@ -27,6 +29,7 @@ public abstract class BaseCodeActionService : IRequestHandl protected readonly ILogger Logger; private readonly CSharpDiagnosticService analyzers; private readonly CachingCodeFixProviderForProjects codeFixesForProject; + private readonly OmniSharpOptions options; private readonly MethodInfo _getNestedCodeActions; protected Lazy> OrderedCodeRefactoringProviders; @@ -37,14 +40,20 @@ public abstract class BaseCodeActionService : IRequestHandl { "CS8019", "RemoveUnnecessaryImportsFixable" } }; - protected BaseCodeActionService(OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, CSharpDiagnosticService analyzers, CachingCodeFixProviderForProjects codeFixesForProject) + protected BaseCodeActionService( + OmniSharpWorkspace workspace, + IEnumerable providers, + ILogger logger, + CSharpDiagnosticService analyzers, + CachingCodeFixProviderForProjects codeFixesForProject, + OmniSharpOptions options) { this.Workspace = workspace; this.Providers = providers; this.Logger = logger; this.analyzers = analyzers; this.codeFixesForProject = codeFixesForProject; - + this.options = options; OrderedCodeRefactoringProviders = new Lazy>(() => GetSortedCodeRefactoringProviders()); // Sadly, the CodeAction.NestedCodeActions property is still internal. @@ -104,10 +113,11 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText) private async Task CollectCodeFixesActions(Document document, TextSpan span, List codeActions) { - var analyzers = await this.analyzers.GetCurrentDiagnosticResult(ImmutableArray.Create(document.Project.Id)); + var diagnostics = this.options.RoslynExtensionsOptions.EnableExpiremantalCodeAnalysis + ? await GetDiagnosticsWithAnalyzers(document) + : (await document.GetSemanticModelAsync()).GetDiagnostics(); - var groupedBySpan = - analyzers.Select(x => x.diagnostic) + var groupedBySpan = diagnostics .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) .GroupBy(diagnostic => diagnostic.Location.SourceSpan); @@ -120,6 +130,11 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis } } + private async Task> GetDiagnosticsWithAnalyzers(Document document) + { + return (await this.analyzers.GetCurrentDiagnosticResult(ImmutableArray.Create(document.Project.Id))).Select(x => x.diagnostic); + } + private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerable diagnostics, List codeActions) { foreach (var codeFixProvider in GetSortedCodeFixProviders(document)) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs index 2bfaaefc24..0c48223032 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using OmniSharp.Mef; using OmniSharp.Models.V2.CodeActions; +using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.CodeActions; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Services; @@ -22,8 +23,9 @@ public GetCodeActionsService( [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, CSharpDiagnosticService analyzers, - CachingCodeFixProviderForProjects codeFixesForProjects) - : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) + CachingCodeFixProviderForProjects codeFixesForProjects, + OmniSharpOptions options) + : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects, options) { } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index 7a0a5cf4e8..b0181ee881 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Logging; using OmniSharp.Mef; using OmniSharp.Models; +using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.CodeActions; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using OmniSharp.Roslyn.Utilities; @@ -41,8 +42,9 @@ public RunCodeActionService( [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, CSharpDiagnosticService analyzers, - CachingCodeFixProviderForProjects codeFixesForProjects) - : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects) + CachingCodeFixProviderForProjects codeFixesForProjects, + OmniSharpOptions options) + : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects, options) { _loader = loader; _workspaceAssembly = _loader.LazyLoad(Configuration.RoslynWorkspaces); From de7426e6a0f0db30d0282dc790580e0c60a4457e Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 15:25:48 +0200 Subject: [PATCH 109/178] Updated flag name. --- src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs | 2 +- .../Services/Diagnostics/CodeCheckService.cs | 2 +- .../Services/Refactoring/V2/BaseCodeActionService.cs | 2 +- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 2 +- tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs b/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs index b94f574965..454fc05502 100644 --- a/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs +++ b/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs @@ -7,7 +7,7 @@ namespace OmniSharp.Options { public class RoslynExtensionsOptions { - public bool EnableExpiremantalCodeAnalysis { get; set; } + public bool EnableCodeAnalysis { get; set; } public string[] LocationPaths { get; set; } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 7027cb8712..a06798353b 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -33,7 +33,7 @@ public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticService ro public async Task Handle(CodeCheckRequest request) { - if(!_options.RoslynExtensionsOptions.EnableExpiremantalCodeAnalysis) + if(!_options.RoslynExtensionsOptions.EnableCodeAnalysis) { var documents = request.FileName != null ? _workspace.GetDocuments(request.FileName) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 014c4d4950..a53304a046 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -113,7 +113,7 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText) private async Task CollectCodeFixesActions(Document document, TextSpan span, List codeActions) { - var diagnostics = this.options.RoslynExtensionsOptions.EnableExpiremantalCodeAnalysis + var diagnostics = this.options.RoslynExtensionsOptions.EnableCodeAnalysis ? await GetDiagnosticsWithAnalyzers(document) : (await document.GetSemanticModelAsync()).GetDiagnostics(); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 10fcc78251..cfba9e8439 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -63,7 +63,7 @@ public CSharpDiagnosticService( .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution) }) ?? throw new InvalidOperationException("Could not resolve 'Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions' for IDE analyzers."); - if (options.RoslynExtensionsOptions.EnableExpiremantalCodeAnalysis) + if (options.RoslynExtensionsOptions.EnableCodeAnalysis) { _workspace.WorkspaceChanged += OnWorkspaceChanged; diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index e0631b028a..4341a166a3 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -45,7 +45,7 @@ public async Task CodeCheckSpecifiedFileOnly(string filename) private CSharpDiagnosticService CreateDiagnosticService(DiagnosticEventForwarder forwarder) { var options = new OmniSharpOptions(); - options.RoslynExtensionsOptions.EnableExpiremantalCodeAnalysis = true; + options.RoslynExtensionsOptions.EnableCodeAnalysis = true; return new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects(), options); } From 633555b76c02eb12ba4aaa93137d4555315f8ced Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 10 Nov 2018 15:36:23 +0200 Subject: [PATCH 110/178] Fixed default. --- tests/TestUtility/OmniSharpTestHost.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 90d8cb324b..16fa1e3480 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -109,7 +109,7 @@ public static OmniSharpTestHost Create( // During testing new analysis service is used as default. if(configurationData == null) { - configurationData = new [] { new KeyValuePair("RoslynExtensionsOptions:EnableExpiremantalCodeAnalysis", "true") }; + configurationData = new [] { new KeyValuePair("RoslynExtensionsOptions:EnableCodeAnalysis", "true") }; } var serviceProvider = TestServiceProvider.Create(testOutput, environment, configurationData, dotNetCliVersion); From 81d743a12b21d95f3d2d85334211f202bb38adfc Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 13 Nov 2018 18:08:52 +0200 Subject: [PATCH 111/178] Renamed flag. --- src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs | 2 +- .../Services/Diagnostics/CodeCheckService.cs | 2 +- .../Services/Refactoring/V2/BaseCodeActionService.cs | 2 +- .../Workers/Diagnostics/CSharpDiagnosticService.cs | 2 +- tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs | 2 +- tests/TestUtility/OmniSharpTestHost.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs b/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs index 454fc05502..879e22b4d8 100644 --- a/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs +++ b/src/OmniSharp.Abstractions/Options/RoslynExtensionsOptions.cs @@ -7,7 +7,7 @@ namespace OmniSharp.Options { public class RoslynExtensionsOptions { - public bool EnableCodeAnalysis { get; set; } + public bool EnableAnalyzersSupport { get; set; } public string[] LocationPaths { get; set; } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index a06798353b..f87c20cf49 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -33,7 +33,7 @@ public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticService ro public async Task Handle(CodeCheckRequest request) { - if(!_options.RoslynExtensionsOptions.EnableCodeAnalysis) + if(!_options.RoslynExtensionsOptions.EnableAnalyzersSupport) { var documents = request.FileName != null ? _workspace.GetDocuments(request.FileName) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index a53304a046..a0277e4edc 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -113,7 +113,7 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText) private async Task CollectCodeFixesActions(Document document, TextSpan span, List codeActions) { - var diagnostics = this.options.RoslynExtensionsOptions.EnableCodeAnalysis + var diagnostics = this.options.RoslynExtensionsOptions.EnableAnalyzersSupport ? await GetDiagnosticsWithAnalyzers(document) : (await document.GetSemanticModelAsync()).GetDiagnostics(); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index cfba9e8439..640a6c5f52 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -63,7 +63,7 @@ public CSharpDiagnosticService( .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution) }) ?? throw new InvalidOperationException("Could not resolve 'Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions' for IDE analyzers."); - if (options.RoslynExtensionsOptions.EnableCodeAnalysis) + if (options.RoslynExtensionsOptions.EnableAnalyzersSupport) { _workspace.WorkspaceChanged += OnWorkspaceChanged; diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index 4341a166a3..0531e20461 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -45,7 +45,7 @@ public async Task CodeCheckSpecifiedFileOnly(string filename) private CSharpDiagnosticService CreateDiagnosticService(DiagnosticEventForwarder forwarder) { var options = new OmniSharpOptions(); - options.RoslynExtensionsOptions.EnableCodeAnalysis = true; + options.RoslynExtensionsOptions.EnableAnalyzersSupport = true; return new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects(), options); } diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 16fa1e3480..011c5c39d6 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -109,7 +109,7 @@ public static OmniSharpTestHost Create( // During testing new analysis service is used as default. if(configurationData == null) { - configurationData = new [] { new KeyValuePair("RoslynExtensionsOptions:EnableCodeAnalysis", "true") }; + configurationData = new [] { new KeyValuePair("RoslynExtensionsOptions:EnableAnalyzersSupport", "true") }; } var serviceProvider = TestServiceProvider.Create(testOutput, environment, configurationData, dotNetCliVersion); From b4a656dd619e47e31414d585bdc50ba08274ffda Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 19 Nov 2018 20:33:58 +0200 Subject: [PATCH 112/178] Initial structure divide for testability. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 82 ++++++++++++++++ .../Diagnostics/CSharpDiagnosticService.cs | 98 +++++-------------- 2 files changed, 106 insertions(+), 74 deletions(-) create mode 100644 src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs new file mode 100644 index 0000000000..3e6420614c --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; + +namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics +{ + public class AnalyzerWorkQueue + { + private readonly int _throttlingMs = 300; + + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); + + private readonly ConcurrentDictionary _blockingWork = new ConcurrentDictionary(); + private ILogger _logger; + + public AnalyzerWorkQueue(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public void PushWork(ProjectId projectId) + { + _workQueue.AddOrUpdate(projectId, + (modified: DateTime.UtcNow, projectId: projectId), + (_, oldValue) => (modified: DateTime.UtcNow, projectId: projectId)); + } + + public ImmutableArray PopWork() + { + lock (_workQueue) + { + var currentWork = _workQueue + .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) + .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. + .Take(1) // Limit mount of work executed by once. This is needed on large solution... + .ToImmutableArray(); + + foreach (var workKey in currentWork.Select(x => x.Key)) + { + _workQueue.TryRemove(workKey, out _); + _blockingWork.TryAdd(workKey, new CancellationTokenSource()); + } + + return currentWork.Select(x => x.Key).ToImmutableArray(); + } + } + + public void AckWork(ProjectId projectId) + { + if(_blockingWork.TryGetValue(projectId, out var tokenSource)) + { + tokenSource.Cancel(); + _blockingWork.TryRemove(projectId, out _); + } + } + + // Omnisharp V2 api expects that it can request current information of diagnostics any time, + // however analysis is worker based and is eventually ready. This method is used to make api look + // like it's syncronous even that actual analysis may take a while. + public async Task WaitForPendingWork(ImmutableArray projectIds) + { + await Task.WhenAll(_blockingWork + .Where(x => projectIds.Any(pid => pid == x.Key)) + .Select(x => Task.Delay(30 * 1000, x.Value.Token) + .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) + .ToImmutableArray()); + } + + // This is basically asserting mechanism for hanging analysis if any. If this doesn't exist tracking + // down why results doesn't come up (for example in situation when theres bad analyzer that takes ages to complete). + private void LogTimeouts(Task task, string description) + { + if (!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); + } + } +} \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs index 640a6c5f52..bf25dce5a7 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs @@ -14,6 +14,7 @@ using OmniSharp.Helpers; using OmniSharp.Models.Diagnostics; using OmniSharp.Options; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; using OmniSharp.Services; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics @@ -22,12 +23,14 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics [Export(typeof(CSharpDiagnosticService))] public class CSharpDiagnosticService { + private readonly AnalyzerWorkQueue _workQueue; private readonly ILogger _logger; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary diagnostics)> _results = new ConcurrentDictionary diagnostics)>(); + private readonly ImmutableArray _providers; + private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; @@ -37,9 +40,6 @@ public class CSharpDiagnosticService // This options gives certain IDE analysis access for services that are not yet publicly available. private readonly ConstructorInfo _workspaceAnalyzerOptionsConstructor; - private CancellationTokenSource _initializationQueueDoneSource = new CancellationTokenSource(); - private readonly int _throttlingMs = 300; - [ImportingConstructor] public CSharpDiagnosticService( OmniSharpWorkspace workspace, @@ -51,7 +51,7 @@ public CSharpDiagnosticService( { _logger = loggerFactory.CreateLogger(); _providers = providers.ToImmutableArray(); - + _workQueue = new AnalyzerWorkQueue(loggerFactory); _forwarder = forwarder; _workspace = workspace; @@ -70,25 +70,23 @@ public CSharpDiagnosticService( Task.Run(async () => { while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); - QueueForAnalysis(workspace.CurrentSolution.Projects.Select(x => x.Id).ToImmutableArray()); - _initializationQueueDoneSource.Cancel(); _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); }); - Task.Factory.StartNew(() => Worker(CancellationToken.None), TaskCreationOptions.LongRunning); + Task.Factory.StartNew(Worker, TaskCreationOptions.LongRunning); } } - private async Task Worker(CancellationToken token) + private async Task Worker() { - while (!token.IsCancellationRequested) + while (true) { try { - var currentWork = GetThrottledWork(); - await Task.WhenAll(currentWork.Select(x => Analyze(x.project, x.workReadySource, token))); - await Task.Delay(100, token); + var currentWork = GetWorkerProjects(); + await Task.WhenAll(currentWork.Select(x => Analyze(x))); + await Task.Delay(100); } catch (Exception ex) { @@ -97,35 +95,17 @@ private async Task Worker(CancellationToken token) } } - private ImmutableArray<(Project project, CancellationTokenSource workReadySource)> GetThrottledWork() + private ImmutableArray GetWorkerProjects() { - lock (_workQueue) - { - var currentWork = _workQueue - .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) - .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. - .Take(2) // Limit mount of work executed by once. This is needed on large solution... - .ToImmutableArray(); - - foreach (var workKey in currentWork.Select(x => x.Key)) - { - _workQueue.TryRemove(workKey, out _); - } - - return currentWork - .Select(x => (project: _workspace?.CurrentSolution?.GetProject(x.Value.projectId), x.Value.workReadySource)) - .Where(x => x.project != null) // This may occur if project removed middle of analysis. - .ToImmutableArray(); - } + return _workQueue.PopWork() + .Select(projectId => _workspace?.CurrentSolution?.GetProject(projectId)) + .Where(project => project != null) // This may occur if project removed middle of analysis from solution. + .ToImmutableArray(); } public async Task> GetCurrentDiagnosticResult(ImmutableArray projectIds) { - await WaitForInitialStartupWorkIfAny(); - - var pendingWork = WaitForPendingWorkIfNeededAndGetIt(projectIds); - - await Task.WhenAll(pendingWork); + await _workQueue.WaitForPendingWork(projectIds); return _results .Where(x => projectIds.Any(pid => pid == x.Key)) @@ -137,40 +117,10 @@ public void QueueForAnalysis(ImmutableArray projects) { foreach (var projectId in projects) { - _workQueue.AddOrUpdate(projectId, - (modified: DateTime.UtcNow, projectId: projectId, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, projectId: projectId, oldValue.workReadySource)); + _workQueue.PushWork(projectId); } } - private ImmutableArray WaitForPendingWorkIfNeededAndGetIt(ImmutableArray projectIds) - { - return _workQueue - .Where(x => projectIds.Any(pid => pid == x.Key)) - .Select(x => Task.Delay(30 * 1000, x.Value.workReadySource.Token) - .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) - .Concat(new[] { Task.Delay(250) }) // Workaround for issue where information about updates from workspace are not at sync with calls. - .ToImmutableArray(); - } - - // Editors seems to fetch initial (get all) diagnostics too soon from api, - // and when this happens initially api returns nothing. This causes nothing - // to show until user action causes editor to re-fetch all diagnostics from api again. - // For this reason initially api waits for results for moment. This isn't perfect - // solution but hopefully works until event based diagnostics are published. - private Task WaitForInitialStartupWorkIfAny() - { - return Task.Delay(30 * 1000, _initializationQueueDoneSource.Token) - .ContinueWith(task => LogTimeouts(task, nameof(_initializationQueueDoneSource))); - } - - // This is basically asserting mechanism for hanging analysis if any. If this doesn't exist tracking - // down why results doesn't come up (for example in situation when theres bad analyzer that takes ages to complete). - private void LogTimeouts(Task task, string description) - { - if (!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); - } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged @@ -182,7 +132,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv } } - private async Task Analyze(Project project, CancellationTokenSource workReadySource, CancellationToken token) + private async Task Analyze(Project project) { try { @@ -201,7 +151,7 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou var compiled = await project.WithCompilationOptions( _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) - .GetCompilationAsync(token); + .GetCompilationAsync(); ImmutableArray results = ImmutableArray.Empty; @@ -212,7 +162,7 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou results = await compiled .WithAnalyzers(allAnalyzers, workspaceAnalyzerOptions) // This cannot be invoked with empty analyzers list. - .GetAllDiagnosticsAsync(token); + .GetAllDiagnosticsAsync(); } else { @@ -229,7 +179,7 @@ private async Task Analyze(Project project, CancellationTokenSource workReadySou } finally { - workReadySource.Cancel(); + _workQueue.AckWork(project.Id); } } @@ -251,7 +201,7 @@ private void EmitDiagnostics(ImmutableArray results) private async Task AnalyzeSingleMiscFilesProject(Project project) { var syntaxTrees = await Task.WhenAll(project.Documents - .Select(async document => await document.GetSyntaxTreeAsync())); + .Select(async document => await document.GetSyntaxTreeAsync())); var results = syntaxTrees .Select(x => x.GetDiagnostics()) From ee55e0da13af0585586fc7d21f85d1a99f4a06b2 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 21 Nov 2018 07:40:36 +0200 Subject: [PATCH 113/178] Few tests. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 7 +- .../AnalyzerWorkerQueueFacts.cs | 97 +++++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 3e6420614c..74f0d57701 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -17,11 +17,14 @@ public class AnalyzerWorkQueue new ConcurrentDictionary(); private readonly ConcurrentDictionary _blockingWork = new ConcurrentDictionary(); + private readonly int _timeoutForPendingWorkMs; private ILogger _logger; - public AnalyzerWorkQueue(ILoggerFactory loggerFactory) + public AnalyzerWorkQueue(ILoggerFactory loggerFactory, int throttleWorkMs = 300, int timeoutForPendingWorkMs = 60*1000) { _logger = loggerFactory.CreateLogger(); + _throttlingMs = throttleWorkMs; + _timeoutForPendingWorkMs = timeoutForPendingWorkMs; } public void PushWork(ProjectId projectId) @@ -67,7 +70,7 @@ public async Task WaitForPendingWork(ImmutableArray projectIds) { await Task.WhenAll(_blockingWork .Where(x => projectIds.Any(pid => pid == x.Key)) - .Select(x => Task.Delay(30 * 1000, x.Value.Token) + .Select(x => Task.Delay(_timeoutForPendingWorkMs, x.Value.Token) .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) .ToImmutableArray()); } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs new file mode 100644 index 0000000000..71f285249b --- /dev/null +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; +using Xunit; + +namespace OmniSharp.Roslyn.CSharp.Tests +{ + public class AnalyzerWorkerQueueFacts + { + private class Logger : ILogger + { + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + } + } + + private class LoggerFactory : ILoggerFactory + { + public void AddProvider(ILoggerProvider provider) + { + } + + public ILogger CreateLogger(string categoryName) + { + return NullLogger.Instance; + } + + public void Dispose() + { + } + } + + [Fact] + public void WhenProjectsAreAddedButThrotlingIsntOverNoProjectsShouldBeReturned() + { + var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 500); + var projectId = ProjectId.CreateNewId(); + + queue.PushWork(projectId); + Assert.Empty(queue.PopWork()); + } + + [Fact] + public void WhenProjectsAreAddedToQueueThenTheyWillBeReturned() + { + var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0); + var projectId = ProjectId.CreateNewId(); + + queue.PushWork(projectId); + + Assert.Contains(projectId, queue.PopWork()); + Assert.Empty(queue.PopWork()); + } + + [Fact] + public async Task WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAsOne() + { + var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 20); + var projectId = ProjectId.CreateNewId(); + + queue.PushWork(projectId); + queue.PushWork(projectId); + queue.PushWork(projectId); + + Assert.Empty(queue.PopWork()); + + await Task.Delay(TimeSpan.FromMilliseconds(40)); + + Assert.Contains(projectId, queue.PopWork()); + Assert.Empty(queue.PopWork()); + } + + [Fact] + public void WhenWorkIsTakenThenItWillBlockWhenWorkIsWaited() + { + // var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 500); + // var projectId = ProjectId.CreateNewId(); + + // queue.PushWork(projectId); + + // var work = queue.PopWork(); + + // queue.WaitForPendingWork(work).Wait(TimeSpan.FromMilliseconds(50)); + } + } +} \ No newline at end of file From ea544636324b18ff0b0d8090961f49e2945c62ed Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 25 Nov 2018 09:14:18 +0200 Subject: [PATCH 114/178] More tests for queue functionality. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 4 +- .../AnalyzerWorkerQueueFacts.cs | 55 ++++++++++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 74f0d57701..5630d3d3ef 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -39,7 +39,7 @@ public ImmutableArray PopWork() lock (_workQueue) { var currentWork = _workQueue - .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) < DateTime.UtcNow) + .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) <= DateTime.UtcNow) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. .Take(1) // Limit mount of work executed by once. This is needed on large solution... .ToImmutableArray(); @@ -82,4 +82,4 @@ private void LogTimeouts(Task task, string description) if (!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); } } -} \ No newline at end of file +} diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 71f285249b..a8b669713a 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -82,16 +82,57 @@ public async Task WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAs } [Fact] - public void WhenWorkIsTakenThenItWillBlockWhenWorkIsWaited() + public void WhenWorkIsAddedThenWaitNextIterationOfItReady() { - // var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 500); - // var projectId = ProjectId.CreateNewId(); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 500); + var projectId = ProjectId.CreateNewId(); + + queue.PushWork(projectId); + + var pendingTask = queue.WaitForPendingWork(new [] { projectId }.ToImmutableArray()); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + + Assert.False(pendingTask.IsCompleted); + + queue.PopWork(); + queue.AckWork(projectId); + Assert.True(pendingTask.IsCompleted); + } + + [Fact] + public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() + { + var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 500); + var projectId = ProjectId.CreateNewId(); + + queue.PushWork(projectId); + + var work = queue.PopWork(); + + var pendingTask = queue.WaitForPendingWork(work); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + + Assert.False(pendingTask.IsCompleted); + + queue.AckWork(projectId); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + Assert.True(pendingTask.IsCompleted); + } + + [Fact] + public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() + { + var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 50); + var projectId = ProjectId.CreateNewId(); + + queue.PushWork(projectId); - // queue.PushWork(projectId); + var work = queue.PopWork(); - // var work = queue.PopWork(); + var pendingTask = queue.WaitForPendingWork(work); + pendingTask.Wait(TimeSpan.FromMilliseconds(100)); - // queue.WaitForPendingWork(work).Wait(TimeSpan.FromMilliseconds(50)); + Assert.True(pendingTask.IsCompleted); } } -} \ No newline at end of file +} From 047d5ed42093031ac81ad0643df465bb8444e529 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 25 Nov 2018 18:41:03 +0200 Subject: [PATCH 115/178] Improved functionality to match exceptations. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 37 +++++++++++-------- .../AnalyzerWorkerQueueFacts.cs | 8 +++- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 5630d3d3ef..ae3b3a0626 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -13,10 +13,11 @@ public class AnalyzerWorkQueue { private readonly int _throttlingMs = 300; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); - private readonly ConcurrentDictionary _blockingWork = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _currentWork = + new ConcurrentDictionary(); private readonly int _timeoutForPendingWorkMs; private ILogger _logger; @@ -30,8 +31,8 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, int throttleWorkMs = 300, public void PushWork(ProjectId projectId) { _workQueue.AddOrUpdate(projectId, - (modified: DateTime.UtcNow, projectId: projectId), - (_, oldValue) => (modified: DateTime.UtcNow, projectId: projectId)); + (modified: DateTime.UtcNow, projectId: projectId, new CancellationTokenSource()), + (_, oldValue) => (modified: DateTime.UtcNow, projectId: projectId, workDoneSource: oldValue.workDoneSource)); } public ImmutableArray PopWork() @@ -44,10 +45,10 @@ public ImmutableArray PopWork() .Take(1) // Limit mount of work executed by once. This is needed on large solution... .ToImmutableArray(); - foreach (var workKey in currentWork.Select(x => x.Key)) + foreach (var work in currentWork) { - _workQueue.TryRemove(workKey, out _); - _blockingWork.TryAdd(workKey, new CancellationTokenSource()); + _workQueue.TryRemove(work.Key, out _); + _currentWork.TryAdd(work.Key, work.Value); } return currentWork.Select(x => x.Key).ToImmutableArray(); @@ -56,10 +57,10 @@ public ImmutableArray PopWork() public void AckWork(ProjectId projectId) { - if(_blockingWork.TryGetValue(projectId, out var tokenSource)) + if(_currentWork.TryGetValue(projectId, out var work)) { - tokenSource.Cancel(); - _blockingWork.TryRemove(projectId, out _); + work.workDoneSource.Cancel(); + _currentWork.TryRemove(projectId, out _); } } @@ -68,11 +69,17 @@ public void AckWork(ProjectId projectId) // like it's syncronous even that actual analysis may take a while. public async Task WaitForPendingWork(ImmutableArray projectIds) { - await Task.WhenAll(_blockingWork + var currentWorkMatches = _currentWork.Where(x => projectIds.Any(pid => pid == x.Key)); + + var pendingWorkThatDoesntExistInCurrentWork = _workQueue .Where(x => projectIds.Any(pid => pid == x.Key)) - .Select(x => Task.Delay(_timeoutForPendingWorkMs, x.Value.Token) - .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) - .ToImmutableArray()); + .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); + + await Task.WhenAll( + currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork) + .Select(x => Task.Delay(_timeoutForPendingWorkMs, x.Value.workDoneSource.Token) + .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) + .ToImmutableArray()); } // This is basically asserting mechanism for hanging analysis if any. If this doesn't exist tracking diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index a8b669713a..22e3d6c534 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -23,17 +23,20 @@ public IDisposable BeginScope(TState state) public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } + + public ImmutableArray RecordedMessages { get; set; } } private class LoggerFactory : ILoggerFactory { + public ILogger _logger = new Logger(); public void AddProvider(ILoggerProvider provider) { } public ILogger CreateLogger(string categoryName) { - return NullLogger.Instance; + return _logger; } public void Dispose() @@ -94,8 +97,9 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() Assert.False(pendingTask.IsCompleted); - queue.PopWork(); + var work = queue.PopWork(); queue.AckWork(projectId); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } From 652851890dcc9f45d7247ce212cc2ec2e8d0bc3d Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 25 Nov 2018 20:10:17 +0200 Subject: [PATCH 116/178] Move towards diagnosis which can be switched by common interface. --- .../Helpers/DiagnosticExtensions.cs | 32 +-- .../Services/Diagnostics/CodeCheckService.cs | 4 +- .../Diagnostics/DiagnosticsService.cs | 4 +- .../Refactoring/V2/BaseCodeActionService.cs | 4 +- .../Refactoring/V2/GetCodeActionsService.cs | 2 +- .../Refactoring/V2/RunCodeActionService.cs | 2 +- .../Diagnostics/CSharpDiagnosticWorker.cs | 183 ++++++++++++++++++ ...=> CSharpDiagnosticWorkerWithAnalyzers.cs} | 20 +- .../Diagnostics/ICsDiagnosticService.cs | 12 ++ .../DiagnosticsV2Facts.cs | 4 +- 10 files changed, 222 insertions(+), 45 deletions(-) create mode 100644 src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs rename src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/{CSharpDiagnosticService.cs => CSharpDiagnosticWorkerWithAnalyzers.cs} (93%) create mode 100644 src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticService.cs diff --git a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs index a1ae8d23b3..f78467d871 100644 --- a/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs +++ b/src/OmniSharp.Roslyn.CSharp/Helpers/DiagnosticExtensions.cs @@ -24,9 +24,9 @@ internal static DiagnosticLocation ToDiagnosticLocation(this Diagnostic diagnost }; } - internal static IEnumerable DistinctDiagnosticLocationsByProject(this IEnumerable<(string projectName, Diagnostic diagnostic)> analyzerResults) + internal static IEnumerable DistinctDiagnosticLocationsByProject(this IEnumerable<(string projectName, Diagnostic diagnostic)> diagnostics) { - return analyzerResults + return diagnostics .Select(x => new { location = x.diagnostic.ToDiagnosticLocation(), @@ -40,33 +40,5 @@ internal static IEnumerable DistinctDiagnosticLocationsByPro return location; }); } - - internal static async Task> FindDiagnosticLocationsAsync(this IEnumerable documents) - { - if (documents == null || !documents.Any()) return Enumerable.Empty(); - - var items = new List(); - foreach (var document in documents) - { - var semanticModel = await document.GetSemanticModelAsync(); - IEnumerable diagnostics = semanticModel.GetDiagnostics(); - - foreach (var quickFix in diagnostics.Select(d => d.ToDiagnosticLocation())) - { - var existingQuickFix = items.FirstOrDefault(q => q.Equals(quickFix)); - if (existingQuickFix == null) - { - quickFix.Projects.Add(document.Project.Name); - items.Add(quickFix); - } - else - { - existingQuickFix.Projects.Add(document.Project.Name); - } - } - } - - return items; - } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index f87c20cf49..e7aca0e25a 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -18,12 +18,12 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class CodeCheckService : IRequestHandler { private readonly OmniSharpWorkspace _workspace; - private readonly CSharpDiagnosticService _roslynAnalyzer; + private readonly CSharpDiagnosticWorkerWithAnalyzers _roslynAnalyzer; private readonly OmniSharpOptions _options; private readonly ILogger _logger; [ImportingConstructor] - public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticService roslynAnalyzer, ILoggerFactory loggerFactory, OmniSharpOptions options) + public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticWorkerWithAnalyzers roslynAnalyzer, ILoggerFactory loggerFactory, OmniSharpOptions options) { _workspace = workspace; _roslynAnalyzer = roslynAnalyzer; diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs index 463542d883..298028dede 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs @@ -13,10 +13,10 @@ public class DiagnosticsService : IRequestHandler : IRequestHandl protected readonly OmniSharpWorkspace Workspace; protected readonly IEnumerable Providers; protected readonly ILogger Logger; - private readonly CSharpDiagnosticService analyzers; + private readonly CSharpDiagnosticWorkerWithAnalyzers analyzers; private readonly CachingCodeFixProviderForProjects codeFixesForProject; private readonly OmniSharpOptions options; private readonly MethodInfo _getNestedCodeActions; @@ -44,7 +44,7 @@ protected BaseCodeActionService( OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, - CSharpDiagnosticService analyzers, + CSharpDiagnosticWorkerWithAnalyzers analyzers, CachingCodeFixProviderForProjects codeFixesForProject, OmniSharpOptions options) { diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs index 0c48223032..10e6e039c8 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs @@ -22,7 +22,7 @@ public GetCodeActionsService( CodeActionHelper helper, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - CSharpDiagnosticService analyzers, + CSharpDiagnosticWorkerWithAnalyzers analyzers, CachingCodeFixProviderForProjects codeFixesForProjects, OmniSharpOptions options) : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects, options) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index b0181ee881..bbec8a14d5 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -41,7 +41,7 @@ public RunCodeActionService( CodeActionHelper helper, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - CSharpDiagnosticService analyzers, + CSharpDiagnosticWorkerWithAnalyzers analyzers, CachingCodeFixProviderForProjects codeFixesForProjects, OmniSharpOptions options) : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects, options) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs new file mode 100644 index 0000000000..9664b8f1e9 --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -0,0 +1,183 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Microsoft.Extensions.Logging; +using OmniSharp.Helpers; +using OmniSharp.Models.Diagnostics; +using OmniSharp.Options; +using OmniSharp.Roslyn; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; +using OmniSharp.Services; + +namespace OmniSharp.Workers.Diagnostics +{ + [Export, Shared] + public class CSharpDiagnosticWorker: ICsDiagnosticWorker + { + private readonly ILogger _logger; + private readonly OmniSharpWorkspace _workspace; + private readonly object _lock = new object(); + private readonly DiagnosticEventForwarder _forwarder; + private bool _queueRunning = false; + private readonly ConcurrentQueue _openDocuments = new ConcurrentQueue(); + + [ImportingConstructor] + public CSharpDiagnosticWorker(OmniSharpWorkspace workspace, DiagnosticEventForwarder forwarder, ILoggerFactory loggerFactory) + { + _workspace = workspace; + _forwarder = forwarder; + _logger = loggerFactory.CreateLogger(); + + _workspace.WorkspaceChanged += OnWorkspaceChanged; + } + + private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) + { + if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged) + { + var newDocument = changeEvent.NewSolution.GetDocument(changeEvent.DocumentId); + + this.EmitDiagnostics(newDocument.FilePath); + foreach (var document in _workspace.GetOpenDocumentIds().Select(x => _workspace.CurrentSolution.GetDocument(x))) + { + this.EmitDiagnostics(document.FilePath); + } + } + } + + private void EmitDiagnostics(params string[] documents) + { + if (_forwarder.IsEnabled) + { + foreach (var document in documents) + { + if (!_openDocuments.Contains(document)) + { + _openDocuments.Enqueue(document); + } + } + + if (!_queueRunning && !_openDocuments.IsEmpty) + { + this.ProcessQueue(); + } + } + } + + private void ProcessQueue() + { + lock (_lock) + { + _queueRunning = true; + } + + Task.Factory.StartNew(async () => + { + await Task.Delay(100); + await Dequeue(); + + if (_openDocuments.IsEmpty) + { + lock (_lock) + { + _queueRunning = false; + } + } + else + { + this.ProcessQueue(); + } + }); + } + + private async Task Dequeue() + { + var tasks = new List>(); + for (var i = 0; i < 50; i++) + { + if (_openDocuments.IsEmpty) + { + break; + } + + if (_openDocuments.TryDequeue(out var filePath)) + { + tasks.Add(this.ProcessNextItem(filePath)); + } + } + + if (!tasks.Any()) return; + + var diagnosticResults = await Task.WhenAll(tasks.ToArray()); + if (diagnosticResults.Any()) + { + var message = new DiagnosticMessage() + { + Results = diagnosticResults + }; + + this._forwarder.Forward(message); + } + + if (_openDocuments.IsEmpty) + { + lock (_lock) + { + _queueRunning = false; + } + } + else + { + this.ProcessQueue(); + } + } + + private async Task ProcessNextItem(string filePath) + { + var documents = _workspace.GetDocuments(filePath); + var semanticModels = await Task.WhenAll(documents.Select(doc => doc.GetSemanticModelAsync())); + + var items = semanticModels + .Select(sm => sm.GetDiagnostics()) + .SelectMany(x => x); + + return new DiagnosticResult() + { + FileName = filePath, + QuickFixes = items.Select(x => x.ToDiagnosticLocation()).Distinct().ToArray() + }; + } + + public void QueueForDiagnosis(ImmutableArray documents) + { + this.EmitDiagnostics(documents.Select(x => x.FilePath).ToArray()); + } + + public async Task> GetDiagnostics(ImmutableArray documents) + { + if (documents == null || !documents.Any()) return ImmutableArray<(string projectName, Diagnostic diagnostic)>.Empty; + + var results = new List<(string projectName, Diagnostic diagnostic)>(); + + foreach(var document in documents) + { + var semanticModel = await document.GetSemanticModelAsync(); + var diagnostics = semanticModel.GetDiagnostics(); + var projectName = document.Project.Name; + + results.AddRange(diagnostics.Select(x => (projectName: document.Project.Name, diagnostic: x))); + } + + return results.ToImmutableArray(); + } + } +} \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs similarity index 93% rename from src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs rename to src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index bf25dce5a7..e6e80b0fd6 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -20,11 +20,11 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { [Shared] - [Export(typeof(CSharpDiagnosticService))] - public class CSharpDiagnosticService + [Export(typeof(CSharpDiagnosticWorkerWithAnalyzers))] + public class CSharpDiagnosticWorkerWithAnalyzers: ICsDiagnosticWorker { private readonly AnalyzerWorkQueue _workQueue; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly ConcurrentDictionary diagnostics)> _results = new ConcurrentDictionary diagnostics)>(); @@ -41,7 +41,7 @@ public class CSharpDiagnosticService private readonly ConstructorInfo _workspaceAnalyzerOptionsConstructor; [ImportingConstructor] - public CSharpDiagnosticService( + public CSharpDiagnosticWorkerWithAnalyzers( OmniSharpWorkspace workspace, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, @@ -49,7 +49,7 @@ public CSharpDiagnosticService( RulesetsForProjects rulesetsForProjects, OmniSharpOptions options) { - _logger = loggerFactory.CreateLogger(); + _logger = loggerFactory.CreateLogger(); _providers = providers.ToImmutableArray(); _workQueue = new AnalyzerWorkQueue(loggerFactory); @@ -209,5 +209,15 @@ private async Task AnalyzeSingleMiscFilesProject(Project project) _results[project.Id] = (project.Name, results.ToImmutableArray()); } + + public Task> GetDiagnostics(ImmutableArray documents) + { + throw new NotImplementedException(); + } + + public void QueueForDiagnosis(ImmutableArray documents) + { + throw new NotImplementedException(); + } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticService.cs new file mode 100644 index 0000000000..fa145eaff1 --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticService.cs @@ -0,0 +1,12 @@ +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; + +namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics +{ + public interface ICsDiagnosticWorker + { + Task> GetDiagnostics(ImmutableArray documents); + void QueueForDiagnosis(ImmutableArray documents); + } +} \ No newline at end of file diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index 0531e20461..aace9393b4 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -42,11 +42,11 @@ public async Task CodeCheckSpecifiedFileOnly(string filename) await emitter.ExpectForEmitted(msg => msg.Results.Any(m => m.FileName == filename)); } - private CSharpDiagnosticService CreateDiagnosticService(DiagnosticEventForwarder forwarder) + private CSharpDiagnosticWorkerWithAnalyzers CreateDiagnosticService(DiagnosticEventForwarder forwarder) { var options = new OmniSharpOptions(); options.RoslynExtensionsOptions.EnableAnalyzersSupport = true; - return new CSharpDiagnosticService(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects(), options); + return new CSharpDiagnosticWorkerWithAnalyzers(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects(), options); } [Theory] From 180b979b407f6a8801ac07e3a0b412575c6708d1 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 25 Nov 2018 21:06:26 +0200 Subject: [PATCH 117/178] Refactored ugly if switching of diagnostics behind common class. --- .../Services/Diagnostics/CodeCheckService.cs | 41 +++++-------- .../Diagnostics/DiagnosticsService.cs | 18 +++--- .../Refactoring/V2/BaseCodeActionService.cs | 24 +++----- .../Refactoring/V2/GetCodeActionsService.cs | 8 +-- .../Refactoring/V2/RunCodeActionService.cs | 8 +-- .../Diagnostics/CSharpDiagnosticWorker.cs | 4 +- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 25 +++----- .../CsharpDiagnosticWorkerStragedy.cs | 59 +++++++++++++++++++ .../DiagnosticsV2Facts.cs | 6 +- 9 files changed, 111 insertions(+), 82 deletions(-) create mode 100644 src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerStragedy.cs diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index e7aca0e25a..c7026105cd 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -11,6 +11,7 @@ using OmniSharp.Models.CodeCheck; using OmniSharp.Models.Diagnostics; using OmniSharp.Options; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { @@ -18,48 +19,38 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class CodeCheckService : IRequestHandler { private readonly OmniSharpWorkspace _workspace; - private readonly CSharpDiagnosticWorkerWithAnalyzers _roslynAnalyzer; private readonly OmniSharpOptions _options; + private readonly ICsDiagnosticWorker _diagWorker; private readonly ILogger _logger; [ImportingConstructor] - public CodeCheckService(OmniSharpWorkspace workspace, CSharpDiagnosticWorkerWithAnalyzers roslynAnalyzer, ILoggerFactory loggerFactory, OmniSharpOptions options) + public CodeCheckService( + OmniSharpWorkspace workspace, + ILoggerFactory loggerFactory, + OmniSharpOptions options, + ICsDiagnosticWorker diagWorker) { _workspace = workspace; - _roslynAnalyzer = roslynAnalyzer; _options = options; + _diagWorker = diagWorker; _logger = loggerFactory.CreateLogger(); } public async Task Handle(CodeCheckRequest request) { - if(!_options.RoslynExtensionsOptions.EnableAnalyzersSupport) - { - var documents = request.FileName != null - ? _workspace.GetDocuments(request.FileName) - : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); + var documents = request.FileName != null + ? _workspace.GetDocuments(request.FileName) + : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); - var quickFixes = await documents.FindDiagnosticLocationsAsync(); - return new QuickFixResponse(quickFixes); - } + var diagnostics = await _diagWorker.GetDiagnostics(documents.ToImmutableArray()); - var projectsForAnalysis = !string.IsNullOrEmpty(request.FileName) - ? new[] { _workspace.GetDocument(request.FileName)?.Project } - : _workspace.CurrentSolution.Projects; - - var analyzerResults = await _roslynAnalyzer.GetCurrentDiagnosticResult( - projectsForAnalysis - .Where(project => project != null) - .Select(project => project.Id) - .ToImmutableArray()); - - var locations = analyzerResults + var locations = diagnostics .Where(x => (string.IsNullOrEmpty(request.FileName) || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) - .DistinctDiagnosticLocationsByProject(); + .DistinctDiagnosticLocationsByProject() + .Where(x => x.FileName != null); - return new QuickFixResponse( - locations.Where(x => x.FileName != null)); + return new QuickFixResponse(locations); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs index 298028dede..b07f2455d6 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis; using OmniSharp.Mef; using OmniSharp.Models.Diagnostics; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { @@ -13,14 +14,14 @@ public class DiagnosticsService : IRequestHandler Handle(DiagnosticsRequest request) @@ -30,14 +31,11 @@ public Task Handle(DiagnosticsRequest request) _forwarder.IsEnabled = true; } - var projectsForAnalysis = !string.IsNullOrEmpty(request.FileName) - ? new[] { _workspace.GetDocument(request.FileName)?.Project } - : _workspace.CurrentSolution.Projects; + var documents = request.FileName != null + ? _workspace.GetDocuments(request.FileName) + : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); - _diagnostics.QueueForAnalysis(projectsForAnalysis - .Where(x => x != null) - .Select(x => x.Id) - .ToImmutableArray()); + _diagWorker.QueueForDiagnosis(documents.ToImmutableArray()); return Task.FromResult(new DiagnosticsResponse()); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index e502ac850b..20de7d6c5c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -17,6 +17,7 @@ using OmniSharp.Models.V2.CodeActions; using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; using OmniSharp.Services; using OmniSharp.Utilities; @@ -27,9 +28,8 @@ public abstract class BaseCodeActionService : IRequestHandl protected readonly OmniSharpWorkspace Workspace; protected readonly IEnumerable Providers; protected readonly ILogger Logger; - private readonly CSharpDiagnosticWorkerWithAnalyzers analyzers; + private readonly ICsDiagnosticWorker diagnostics; private readonly CachingCodeFixProviderForProjects codeFixesForProject; - private readonly OmniSharpOptions options; private readonly MethodInfo _getNestedCodeActions; protected Lazy> OrderedCodeRefactoringProviders; @@ -44,16 +44,14 @@ protected BaseCodeActionService( OmniSharpWorkspace workspace, IEnumerable providers, ILogger logger, - CSharpDiagnosticWorkerWithAnalyzers analyzers, - CachingCodeFixProviderForProjects codeFixesForProject, - OmniSharpOptions options) + ICsDiagnosticWorker diagnostics, + CachingCodeFixProviderForProjects codeFixesForProject) { this.Workspace = workspace; this.Providers = providers; this.Logger = logger; - this.analyzers = analyzers; + this.diagnostics = diagnostics; this.codeFixesForProject = codeFixesForProject; - this.options = options; OrderedCodeRefactoringProviders = new Lazy>(() => GetSortedCodeRefactoringProviders()); // Sadly, the CodeAction.NestedCodeActions property is still internal. @@ -113,11 +111,10 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText) private async Task CollectCodeFixesActions(Document document, TextSpan span, List codeActions) { - var diagnostics = this.options.RoslynExtensionsOptions.EnableAnalyzersSupport - ? await GetDiagnosticsWithAnalyzers(document) - : (await document.GetSemanticModelAsync()).GetDiagnostics(); + var diagnosticsWithProjects = await this.diagnostics.GetDiagnostics(ImmutableArray.Create(document)); - var groupedBySpan = diagnostics + var groupedBySpan = diagnosticsWithProjects + .Select(x => x.diagnostic) .Where(diagnostic => span.IntersectsWith(diagnostic.Location.SourceSpan)) .GroupBy(diagnostic => diagnostic.Location.SourceSpan); @@ -130,11 +127,6 @@ private async Task CollectCodeFixesActions(Document document, TextSpan span, Lis } } - private async Task> GetDiagnosticsWithAnalyzers(Document document) - { - return (await this.analyzers.GetCurrentDiagnosticResult(ImmutableArray.Create(document.Project.Id))).Select(x => x.diagnostic); - } - private async Task AppendFixesAsync(Document document, TextSpan span, IEnumerable diagnostics, List codeActions) { foreach (var codeFixProvider in GetSortedCodeFixProviders(document)) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs index 10e6e039c8..5ade3b1ca8 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/GetCodeActionsService.cs @@ -9,6 +9,7 @@ using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.CodeActions; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; using OmniSharp.Services; namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 @@ -22,10 +23,9 @@ public GetCodeActionsService( CodeActionHelper helper, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - CSharpDiagnosticWorkerWithAnalyzers analyzers, - CachingCodeFixProviderForProjects codeFixesForProjects, - OmniSharpOptions options) - : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects, options) + ICsDiagnosticWorker diagnostics, + CachingCodeFixProviderForProjects codeFixesForProjects) + : base(workspace, providers, loggerFactory.CreateLogger(), diagnostics, codeFixesForProjects) { } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs index bbec8a14d5..5528a10420 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/RunCodeActionService.cs @@ -14,6 +14,7 @@ using OmniSharp.Options; using OmniSharp.Roslyn.CSharp.Services.CodeActions; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; using OmniSharp.Roslyn.Utilities; using OmniSharp.Services; using OmniSharp.Utilities; @@ -41,10 +42,9 @@ public RunCodeActionService( CodeActionHelper helper, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, - CSharpDiagnosticWorkerWithAnalyzers analyzers, - CachingCodeFixProviderForProjects codeFixesForProjects, - OmniSharpOptions options) - : base(workspace, providers, loggerFactory.CreateLogger(), analyzers, codeFixesForProjects, options) + ICsDiagnosticWorker diagnostics, + CachingCodeFixProviderForProjects codeFixesForProjects) + : base(workspace, providers, loggerFactory.CreateLogger(), diagnostics, codeFixesForProjects) { _loader = loader; _workspaceAssembly = _loader.LazyLoad(Configuration.RoslynWorkspaces); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 9664b8f1e9..a5a104e5fa 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -18,9 +18,8 @@ using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; using OmniSharp.Services; -namespace OmniSharp.Workers.Diagnostics +namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics { - [Export, Shared] public class CSharpDiagnosticWorker: ICsDiagnosticWorker { private readonly ILogger _logger; @@ -30,7 +29,6 @@ public class CSharpDiagnosticWorker: ICsDiagnosticWorker private bool _queueRunning = false; private readonly ConcurrentQueue _openDocuments = new ConcurrentQueue(); - [ImportingConstructor] public CSharpDiagnosticWorker(OmniSharpWorkspace workspace, DiagnosticEventForwarder forwarder, ILoggerFactory loggerFactory) { _workspace = workspace; diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index e6e80b0fd6..228b380097 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -19,8 +19,6 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { - [Shared] - [Export(typeof(CSharpDiagnosticWorkerWithAnalyzers))] public class CSharpDiagnosticWorkerWithAnalyzers: ICsDiagnosticWorker { private readonly AnalyzerWorkQueue _workQueue; @@ -40,14 +38,12 @@ public class CSharpDiagnosticWorkerWithAnalyzers: ICsDiagnosticWorker // This options gives certain IDE analysis access for services that are not yet publicly available. private readonly ConstructorInfo _workspaceAnalyzerOptionsConstructor; - [ImportingConstructor] public CSharpDiagnosticWorkerWithAnalyzers( OmniSharpWorkspace workspace, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, DiagnosticEventForwarder forwarder, - RulesetsForProjects rulesetsForProjects, - OmniSharpOptions options) + RulesetsForProjects rulesetsForProjects) { _logger = loggerFactory.CreateLogger(); _providers = providers.ToImmutableArray(); @@ -63,19 +59,16 @@ public CSharpDiagnosticWorkerWithAnalyzers( .GetConstructor(new Type[] { typeof(AnalyzerOptions), typeof(OptionSet), typeof(Solution) }) ?? throw new InvalidOperationException("Could not resolve 'Microsoft.CodeAnalysis.Diagnostics.WorkspaceAnalyzerOptions' for IDE analyzers."); - if (options.RoslynExtensionsOptions.EnableAnalyzersSupport) - { - _workspace.WorkspaceChanged += OnWorkspaceChanged; + _workspace.WorkspaceChanged += OnWorkspaceChanged; - Task.Run(async () => - { - while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); - QueueForAnalysis(workspace.CurrentSolution.Projects.Select(x => x.Id).ToImmutableArray()); - _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); - }); + Task.Run(async () => + { + while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); + QueueForAnalysis(workspace.CurrentSolution.Projects.Select(x => x.Id).ToImmutableArray()); + _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); + }); - Task.Factory.StartNew(Worker, TaskCreationOptions.LongRunning); - } + Task.Factory.StartNew(Worker, TaskCreationOptions.LongRunning); } private async Task Worker() diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerStragedy.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerStragedy.cs new file mode 100644 index 0000000000..2f59fab1b1 --- /dev/null +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerStragedy.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Options; +using Microsoft.Extensions.Logging; +using OmniSharp.Helpers; +using OmniSharp.Models.Diagnostics; +using OmniSharp.Options; +using OmniSharp.Roslyn.CSharp.Services.Diagnostics; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; +using OmniSharp.Services; + +namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics +{ + // Theres several implementation of worker currently based on configuration. + // This will handle switching between them. + [Export(typeof(ICsDiagnosticWorker)), Shared] + public class CsharpDiagnosticWorkerStragedy: ICsDiagnosticWorker + { + private readonly ICsDiagnosticWorker _implementation; + + [ImportingConstructor] + public CsharpDiagnosticWorkerStragedy( + OmniSharpWorkspace workspace, + [ImportMany] IEnumerable providers, + ILoggerFactory loggerFactory, + DiagnosticEventForwarder forwarder, + RulesetsForProjects rulesetsForProjects, + OmniSharpOptions options) + { + if(options.RoslynExtensionsOptions.EnableAnalyzersSupport) + { + _implementation = new CSharpDiagnosticWorkerWithAnalyzers(workspace, providers, loggerFactory, forwarder, rulesetsForProjects); + } + else + { + _implementation = new CSharpDiagnosticWorker(workspace, forwarder, loggerFactory); + } + } + + public Task> GetDiagnostics(ImmutableArray documents) + { + return _implementation.GetDiagnostics(documents); + } + + public void QueueForDiagnosis(ImmutableArray documents) + { + _implementation.QueueForDiagnosis(documents); + } + } +} \ No newline at end of file diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index aace9393b4..c791106951 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -44,9 +44,7 @@ public async Task CodeCheckSpecifiedFileOnly(string filename) private CSharpDiagnosticWorkerWithAnalyzers CreateDiagnosticService(DiagnosticEventForwarder forwarder) { - var options = new OmniSharpOptions(); - options.RoslynExtensionsOptions.EnableAnalyzersSupport = true; - return new CSharpDiagnosticWorkerWithAnalyzers(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects(), options); + return new CSharpDiagnosticWorkerWithAnalyzers(SharedOmniSharpTestHost.Workspace, Enumerable.Empty(), this.LoggerFactory, forwarder, new RulesetsForProjects()); } [Theory] @@ -83,7 +81,7 @@ public async Task EnablesWhenEndPointIsHit(string filename1, string filename2) var testFile1 = new TestFile(filename1, "class C1 { int n = true; }"); var testFile2 = new TestFile(filename2, "class C2 { int n = true; }"); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile1, testFile2); - + var emitter = new DiagnosticTestEmitter(); var forwarder = new DiagnosticEventForwarder(emitter); var service = CreateDiagnosticService(forwarder); From 7ad625c311c5304a911fcc54bb8cac27229e51fe Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 26 Nov 2018 19:05:18 +0200 Subject: [PATCH 118/178] Implementation of api in worker with analyzers. --- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 23 +++++++++++++++---- ...y.cs => CsharpDiagnosticWorkerComposer.cs} | 4 ++-- 2 files changed, 21 insertions(+), 6 deletions(-) rename src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/{CsharpDiagnosticWorkerStragedy.cs => CsharpDiagnosticWorkerComposer.cs} (94%) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 228b380097..d768f6a3ca 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -106,7 +106,7 @@ private ImmutableArray GetWorkerProjects() .ToImmutableArray(); } - public void QueueForAnalysis(ImmutableArray projects) + private void QueueForAnalysis(ImmutableArray projects) { foreach (var projectId in projects) { @@ -203,14 +203,29 @@ private async Task AnalyzeSingleMiscFilesProject(Project project) _results[project.Id] = (project.Name, results.ToImmutableArray()); } - public Task> GetDiagnostics(ImmutableArray documents) + public async Task> GetDiagnostics(ImmutableArray documents) { - throw new NotImplementedException(); + var projectIds = GetProjectIdsFromDocuments(documents); + + await _workQueue.WaitForPendingWork(projectIds); + + return _results + .Where(x => projectIds.Any(pid => pid == x.Key)) + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))) + .ToImmutableArray(); + } + + private static ImmutableArray GetProjectIdsFromDocuments(ImmutableArray documents) + { + return documents + .Select(x => x.Project?.Id) + .Where(x => x != null) + .ToImmutableArray(); } public void QueueForDiagnosis(ImmutableArray documents) { - throw new NotImplementedException(); + QueueForAnalysis(GetProjectIdsFromDocuments(documents)); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerStragedy.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs similarity index 94% rename from src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerStragedy.cs rename to src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs index 2f59fab1b1..a310807e9f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerStragedy.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs @@ -23,12 +23,12 @@ namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics // Theres several implementation of worker currently based on configuration. // This will handle switching between them. [Export(typeof(ICsDiagnosticWorker)), Shared] - public class CsharpDiagnosticWorkerStragedy: ICsDiagnosticWorker + public class CsharpDiagnosticWorkerComposer: ICsDiagnosticWorker { private readonly ICsDiagnosticWorker _implementation; [ImportingConstructor] - public CsharpDiagnosticWorkerStragedy( + public CsharpDiagnosticWorkerComposer( OmniSharpWorkspace workspace, [ImportMany] IEnumerable providers, ILoggerFactory loggerFactory, From 8b1b499705cb57c44a64c8382ccf406fb080db55 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 26 Nov 2018 19:29:59 +0200 Subject: [PATCH 119/178] Small refactoring. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 6 +- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 55 +++++++------------ .../AnalyzerWorkerQueueFacts.cs | 38 ++++++------- 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index ae3b3a0626..fc438140ee 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -28,14 +28,14 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, int throttleWorkMs = 300, _timeoutForPendingWorkMs = timeoutForPendingWorkMs; } - public void PushWork(ProjectId projectId) + public void PutWork(ProjectId projectId) { _workQueue.AddOrUpdate(projectId, (modified: DateTime.UtcNow, projectId: projectId, new CancellationTokenSource()), (_, oldValue) => (modified: DateTime.UtcNow, projectId: projectId, workDoneSource: oldValue.workDoneSource)); } - public ImmutableArray PopWork() + public ImmutableArray TakeWork() { lock (_workQueue) { @@ -55,7 +55,7 @@ public ImmutableArray PopWork() } } - public void AckWork(ProjectId projectId) + public void AckWorkAsDone(ProjectId projectId) { if(_currentWork.TryGetValue(projectId, out var work)) { diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index d768f6a3ca..14818d37b8 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -23,12 +23,9 @@ public class CSharpDiagnosticWorkerWithAnalyzers: ICsDiagnosticWorker { private readonly AnalyzerWorkQueue _workQueue; private readonly ILogger _logger; - private readonly ConcurrentDictionary diagnostics)> _results = new ConcurrentDictionary diagnostics)>(); - private readonly ImmutableArray _providers; - private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; private readonly RulesetsForProjects _rulesetsForProjects; @@ -63,7 +60,7 @@ public CSharpDiagnosticWorkerWithAnalyzers( Task.Run(async () => { - while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(500); + while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(200); QueueForAnalysis(workspace.CurrentSolution.Projects.Select(x => x.Id).ToImmutableArray()); _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); }); @@ -71,6 +68,23 @@ public CSharpDiagnosticWorkerWithAnalyzers( Task.Factory.StartNew(Worker, TaskCreationOptions.LongRunning); } + public void QueueForDiagnosis(ImmutableArray documents) + { + QueueForAnalysis(GetProjectIdsFromDocuments(documents)); + } + + public async Task> GetDiagnostics(ImmutableArray documents) + { + var projectIds = GetProjectIdsFromDocuments(documents); + + await _workQueue.WaitForPendingWork(projectIds); + + return _results + .Where(x => projectIds.Any(pid => pid == x.Key)) + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))) + .ToImmutableArray(); + } + private async Task Worker() { while (true) @@ -90,27 +104,17 @@ private async Task Worker() private ImmutableArray GetWorkerProjects() { - return _workQueue.PopWork() + return _workQueue.TakeWork() .Select(projectId => _workspace?.CurrentSolution?.GetProject(projectId)) .Where(project => project != null) // This may occur if project removed middle of analysis from solution. .ToImmutableArray(); } - public async Task> GetCurrentDiagnosticResult(ImmutableArray projectIds) - { - await _workQueue.WaitForPendingWork(projectIds); - - return _results - .Where(x => projectIds.Any(pid => pid == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))) - .ToImmutableArray(); - } - private void QueueForAnalysis(ImmutableArray projects) { foreach (var projectId in projects) { - _workQueue.PushWork(projectId); + _workQueue.PutWork(projectId); } } @@ -172,7 +176,7 @@ private async Task Analyze(Project project) } finally { - _workQueue.AckWork(project.Id); + _workQueue.AckWorkAsDone(project.Id); } } @@ -203,18 +207,6 @@ private async Task AnalyzeSingleMiscFilesProject(Project project) _results[project.Id] = (project.Name, results.ToImmutableArray()); } - public async Task> GetDiagnostics(ImmutableArray documents) - { - var projectIds = GetProjectIdsFromDocuments(documents); - - await _workQueue.WaitForPendingWork(projectIds); - - return _results - .Where(x => projectIds.Any(pid => pid == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))) - .ToImmutableArray(); - } - private static ImmutableArray GetProjectIdsFromDocuments(ImmutableArray documents) { return documents @@ -222,10 +214,5 @@ private static ImmutableArray GetProjectIdsFromDocuments(ImmutableArr .Where(x => x != null) .ToImmutableArray(); } - - public void QueueForDiagnosis(ImmutableArray documents) - { - QueueForAnalysis(GetProjectIdsFromDocuments(documents)); - } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 22e3d6c534..25ff99ffe6 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -50,8 +50,8 @@ public void WhenProjectsAreAddedButThrotlingIsntOverNoProjectsShouldBeReturned() var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 500); var projectId = ProjectId.CreateNewId(); - queue.PushWork(projectId); - Assert.Empty(queue.PopWork()); + queue.PutWork(projectId); + Assert.Empty(queue.TakeWork()); } [Fact] @@ -60,10 +60,10 @@ public void WhenProjectsAreAddedToQueueThenTheyWillBeReturned() var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0); var projectId = ProjectId.CreateNewId(); - queue.PushWork(projectId); + queue.PutWork(projectId); - Assert.Contains(projectId, queue.PopWork()); - Assert.Empty(queue.PopWork()); + Assert.Contains(projectId, queue.TakeWork()); + Assert.Empty(queue.TakeWork()); } [Fact] @@ -72,16 +72,16 @@ public async Task WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAs var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 20); var projectId = ProjectId.CreateNewId(); - queue.PushWork(projectId); - queue.PushWork(projectId); - queue.PushWork(projectId); + queue.PutWork(projectId); + queue.PutWork(projectId); + queue.PutWork(projectId); - Assert.Empty(queue.PopWork()); + Assert.Empty(queue.TakeWork()); await Task.Delay(TimeSpan.FromMilliseconds(40)); - Assert.Contains(projectId, queue.PopWork()); - Assert.Empty(queue.PopWork()); + Assert.Contains(projectId, queue.TakeWork()); + Assert.Empty(queue.TakeWork()); } [Fact] @@ -90,15 +90,15 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 500); var projectId = ProjectId.CreateNewId(); - queue.PushWork(projectId); + queue.PutWork(projectId); var pendingTask = queue.WaitForPendingWork(new [] { projectId }.ToImmutableArray()); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); - var work = queue.PopWork(); - queue.AckWork(projectId); + var work = queue.TakeWork(); + queue.AckWorkAsDone(projectId); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -109,16 +109,16 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 500); var projectId = ProjectId.CreateNewId(); - queue.PushWork(projectId); + queue.PutWork(projectId); - var work = queue.PopWork(); + var work = queue.TakeWork(); var pendingTask = queue.WaitForPendingWork(work); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); - queue.AckWork(projectId); + queue.AckWorkAsDone(projectId); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -129,9 +129,9 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 50); var projectId = ProjectId.CreateNewId(); - queue.PushWork(projectId); + queue.PutWork(projectId); - var work = queue.PopWork(); + var work = queue.TakeWork(); var pendingTask = queue.WaitForPendingWork(work); pendingTask.Wait(TimeSpan.FromMilliseconds(100)); From 5e1ab8cb227f58e27ecc68c6d7092a4c298ea466 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 26 Nov 2018 20:36:24 +0200 Subject: [PATCH 120/178] Added logging when codefix provider cannot be created. --- .../V2/CachingCodeFixProviderForProjects.cs | 29 +++++++++++++++---- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 4 +-- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs index d97cfb77c2..48d7c5e95f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.Extensions.Logging; using OmniSharp.Services; using OmniSharp.Utilities; @@ -18,6 +19,13 @@ namespace OmniSharp.Roslyn.CSharp.Services.Refactoring.V2 public class CachingCodeFixProviderForProjects { private readonly ConcurrentDictionary> _cache = new ConcurrentDictionary>(); + private readonly ILogger _logger; + + [ImportingConstructor] + public CachingCodeFixProviderForProjects(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } public IEnumerable GetAllCodeFixesForProject(ProjectId projectId) { @@ -34,14 +42,25 @@ public void LoadFrom(ProjectInfo project) .Where(x => x.IsSubclassOf(typeof(CodeFixProvider))) .Select(x => { - var attribute = x.GetCustomAttribute(); - - if (attribute?.Languages != null && attribute.Languages.Contains(project.Language)) + try { - return (CodeFixProvider)Activator.CreateInstance(x.AsType()); + var attribute = x.GetCustomAttribute(); + + if (attribute?.Languages != null && attribute.Languages.Contains(project.Language)) + { + return (CodeFixProvider)Activator.CreateInstance(x.AsType()); + } + + _logger.LogInformation($"Skipping code fix provider '{x.AsType()}' because it's language doesn't match '{project.Language}'."); + + return null; } + catch (Exception ex) + { + _logger.LogError($"Creating instance of code fix provider '{x.AsType()}' failed, error: {ex}"); - return null; + return null; + } }) .Where(x => x != null) .ToImmutableArray(); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 14818d37b8..1d7f36e42d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -91,7 +91,7 @@ private async Task Worker() { try { - var currentWork = GetWorkerProjects(); + var currentWork = TakeNextBatchOfProjectsForAnalysisIfAny(); await Task.WhenAll(currentWork.Select(x => Analyze(x))); await Task.Delay(100); } @@ -102,7 +102,7 @@ private async Task Worker() } } - private ImmutableArray GetWorkerProjects() + private ImmutableArray TakeNextBatchOfProjectsForAnalysisIfAny() { return _workQueue.TakeWork() .Select(projectId => _workspace?.CurrentSolution?.GetProject(projectId)) From bb5a8965fde98a11a1d254f91115fe1ad4092c30 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 6 Dec 2018 20:55:26 +0200 Subject: [PATCH 121/178] Moved to more robust time control mechanism. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 13 +++++++--- .../AnalyzerWorkerQueueFacts.cs | 26 +++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index fc438140ee..e8341a82f9 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -16,15 +16,19 @@ public class AnalyzerWorkQueue private readonly ConcurrentDictionary _workQueue = new ConcurrentDictionary(); - private readonly ConcurrentDictionary _currentWork = + private readonly ConcurrentDictionary _currentWork = new ConcurrentDictionary(); + private readonly Func _utcNow; private readonly int _timeoutForPendingWorkMs; private ILogger _logger; - public AnalyzerWorkQueue(ILoggerFactory loggerFactory, int throttleWorkMs = 300, int timeoutForPendingWorkMs = 60*1000) + public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 60*1000) { + if(utcNow == null) + utcNow = () => DateTime.UtcNow; + _logger = loggerFactory.CreateLogger(); - _throttlingMs = throttleWorkMs; + _utcNow = utcNow; _timeoutForPendingWorkMs = timeoutForPendingWorkMs; } @@ -39,8 +43,9 @@ public ImmutableArray TakeWork() { lock (_workQueue) { + var now = _utcNow(); var currentWork = _workQueue - .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) <= DateTime.UtcNow) + .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) <= now) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. .Take(1) // Limit mount of work executed by once. This is needed on large solution... .ToImmutableArray(); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 25ff99ffe6..d7e25911e5 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -47,7 +47,8 @@ public void Dispose() [Fact] public void WhenProjectsAreAddedButThrotlingIsntOverNoProjectsShouldBeReturned() { - var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 500); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); var projectId = ProjectId.CreateNewId(); queue.PutWork(projectId); @@ -57,19 +58,23 @@ public void WhenProjectsAreAddedButThrotlingIsntOverNoProjectsShouldBeReturned() [Fact] public void WhenProjectsAreAddedToQueueThenTheyWillBeReturned() { - var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); var projectId = ProjectId.CreateNewId(); queue.PutWork(projectId); + now = PassOverThrotlingPeriod(now); + Assert.Contains(projectId, queue.TakeWork()); Assert.Empty(queue.TakeWork()); } [Fact] - public async Task WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAsOne() + public void WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAsOne() { - var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 20); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); var projectId = ProjectId.CreateNewId(); queue.PutWork(projectId); @@ -78,16 +83,19 @@ public async Task WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAs Assert.Empty(queue.TakeWork()); - await Task.Delay(TimeSpan.FromMilliseconds(40)); + now = PassOverThrotlingPeriod(now); Assert.Contains(projectId, queue.TakeWork()); Assert.Empty(queue.TakeWork()); } + private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(1); + [Fact] public void WhenWorkIsAddedThenWaitNextIterationOfItReady() { - var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 500); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); var projectId = ProjectId.CreateNewId(); queue.PutWork(projectId); @@ -106,7 +114,8 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() [Fact] public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() { - var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 500); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); var projectId = ProjectId.CreateNewId(); queue.PutWork(projectId); @@ -126,7 +135,8 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR [Fact] public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() { - var queue = new AnalyzerWorkQueue(new LoggerFactory(), throttleWorkMs: 0, timeoutForPendingWorkMs: 50); + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 50); var projectId = ProjectId.CreateNewId(); queue.PutWork(projectId); From b22d717faf10234042722edc8acaecc886fedeee Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 6 Dec 2018 21:05:00 +0200 Subject: [PATCH 122/178] Throttling period refactoring. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 9 +++++++-- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- .../AnalyzerWorkerQueueFacts.cs | 11 ++++++++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index e8341a82f9..281703dd38 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -45,7 +45,7 @@ public ImmutableArray TakeWork() { var now = _utcNow(); var currentWork = _workQueue - .Where(x => x.Value.modified.AddMilliseconds(_throttlingMs) <= now) + .Where(x => ThrottlingPeriodNotActive(x.Value.modified, now)) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. .Take(1) // Limit mount of work executed by once. This is needed on large solution... .ToImmutableArray(); @@ -60,6 +60,11 @@ public ImmutableArray TakeWork() } } + private bool ThrottlingPeriodNotActive(DateTime modified, DateTime now) + { + return (now - modified).TotalMilliseconds >= _throttlingMs; + } + public void AckWorkAsDone(ProjectId projectId) { if(_currentWork.TryGetValue(projectId, out var work)) @@ -72,7 +77,7 @@ public void AckWorkAsDone(ProjectId projectId) // Omnisharp V2 api expects that it can request current information of diagnostics any time, // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. - public async Task WaitForPendingWork(ImmutableArray projectIds) + public async Task WaitForPendingWorkDoneEvent(ImmutableArray projectIds) { var currentWorkMatches = _currentWork.Where(x => projectIds.Any(pid => pid == x.Key)); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 1d7f36e42d..a0985acaf9 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -77,7 +77,7 @@ public void QueueForDiagnosis(ImmutableArray documents) { var projectIds = GetProjectIdsFromDocuments(documents); - await _workQueue.WaitForPendingWork(projectIds); + await _workQueue.WaitForPendingWorkDoneEvent(projectIds); return _results .Where(x => projectIds.Any(pid => pid == x.Key)) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index d7e25911e5..e8f604f045 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -100,11 +100,13 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() queue.PutWork(projectId); - var pendingTask = queue.WaitForPendingWork(new [] { projectId }.ToImmutableArray()); + var pendingTask = queue.WaitForPendingWorkDoneEvent(new [] { projectId }.ToImmutableArray()); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); + now = PassOverThrotlingPeriod(now); + var work = queue.TakeWork(); queue.AckWorkAsDone(projectId); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); @@ -120,9 +122,11 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR queue.PutWork(projectId); + now = PassOverThrotlingPeriod(now); + var work = queue.TakeWork(); - var pendingTask = queue.WaitForPendingWork(work); + var pendingTask = queue.WaitForPendingWorkDoneEvent(work); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -141,9 +145,10 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() queue.PutWork(projectId); + now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitForPendingWork(work); + var pendingTask = queue.WaitForPendingWorkDoneEvent(work); pendingTask.Wait(TimeSpan.FromMilliseconds(100)); Assert.True(pendingTask.IsCompleted); From d37af466fdeed5c6e67f67b7b4a25bac0ff480a0 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 17 Jan 2019 16:55:05 +0200 Subject: [PATCH 123/178] Small review fix. --- global.json | 1 - .../Workers/Diagnostics/CSharpDiagnosticWorker.cs | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/global.json b/global.json index 4628116978..b7b530d442 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,4 @@ { "sdk": { - "version": "2.1.301" } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index a5a104e5fa..30f0a5700e 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -145,8 +145,7 @@ private async Task ProcessNextItem(string filePath) var semanticModels = await Task.WhenAll(documents.Select(doc => doc.GetSemanticModelAsync())); var items = semanticModels - .Select(sm => sm.GetDiagnostics()) - .SelectMany(x => x); + .SelectMany(sm => sm.GetDiagnostics()); return new DiagnosticResult() { From 3ebb4fdf231fb361c5fc3b3d958409ab61ed2791 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 17 Jan 2019 17:01:07 +0200 Subject: [PATCH 124/178] Small review update. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 2 +- .../Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- .../OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 281703dd38..b53c3a69b2 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -65,7 +65,7 @@ private bool ThrottlingPeriodNotActive(DateTime modified, DateTime now) return (now - modified).TotalMilliseconds >= _throttlingMs; } - public void AckWorkAsDone(ProjectId projectId) + public void MarkWorkAsCompleteForProject(ProjectId projectId) { if(_currentWork.TryGetValue(projectId, out var work)) { diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index a0985acaf9..a7b673dfc6 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -176,7 +176,7 @@ private async Task Analyze(Project project) } finally { - _workQueue.AckWorkAsDone(project.Id); + _workQueue.MarkWorkAsCompleteForProject(project.Id); } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index e8f604f045..e02cba8d27 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -108,7 +108,7 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - queue.AckWorkAsDone(projectId); + queue.MarkWorkAsCompleteForProject(projectId); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -131,7 +131,7 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR Assert.False(pendingTask.IsCompleted); - queue.AckWorkAsDone(projectId); + queue.MarkWorkAsCompleteForProject(projectId); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } From 6bd21bbff8ff3dde9cc2ff47ca5b80c86f678879 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 17 Jan 2019 17:02:20 +0200 Subject: [PATCH 125/178] Typofix. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index b53c3a69b2..b16b4e1962 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -47,7 +47,7 @@ public ImmutableArray TakeWork() var currentWork = _workQueue .Where(x => ThrottlingPeriodNotActive(x.Value.modified, now)) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. - .Take(1) // Limit mount of work executed by once. This is needed on large solution... + .Take(1) // Limit amount of work executed by once. This is needed on large solution... .ToImmutableArray(); foreach (var work in currentWork) From 4ac48b9875d915cec1a1805281e442ebcc9d5f4a Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 17 Jan 2019 17:04:02 +0200 Subject: [PATCH 126/178] Removed unused member. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index b16b4e1962..6ac8b15cae 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -13,11 +13,11 @@ public class AnalyzerWorkQueue { private readonly int _throttlingMs = 300; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); - private readonly ConcurrentDictionary _currentWork = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _currentWork = + new ConcurrentDictionary(); private readonly Func _utcNow; private readonly int _timeoutForPendingWorkMs; private ILogger _logger; @@ -35,8 +35,8 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = n public void PutWork(ProjectId projectId) { _workQueue.AddOrUpdate(projectId, - (modified: DateTime.UtcNow, projectId: projectId, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, projectId: projectId, workDoneSource: oldValue.workDoneSource)); + (modified: DateTime.UtcNow, new CancellationTokenSource()), + (_, oldValue) => (modified: DateTime.UtcNow, oldValue.workDoneSource)); } public ImmutableArray TakeWork() From 6ce28fc11e4901f0e4501157f06a317943c28c86 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 18 Jan 2019 10:53:08 +0200 Subject: [PATCH 127/178] Testfix. --- tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs index 6a8987e24e..def1c492cc 100644 --- a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs @@ -64,6 +64,7 @@ public void Whatever() { "using System.Text.RegularExpressions;", "System.Text.RegularExpressions.Regex", + "Fix formatting", "Extract Method" }; Assert.Equal(expected, refactorings); From eb313ddf9a73f7689b9d3e7361cce08c00a6a34b Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 18 Jan 2019 10:59:02 +0200 Subject: [PATCH 128/178] Testfix. --- tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs index 487d913851..7049a28dbc 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs @@ -124,6 +124,7 @@ public void Whatever() "Generate type 'Console' -> Generate class 'Console' in new file", "Generate type 'Console' -> Generate class 'Console'", "Generate type 'Console' -> Generate nested class 'Console'", + "Fix formatting", "Extract Method" }; Assert.Equal(expected, refactorings); From d5b69d2cd2963502643573a774f613e4471a2053 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 18 Jan 2019 12:45:53 +0200 Subject: [PATCH 129/178] Restored sdk version to global.json. --- global.json | 1 + 1 file changed, 1 insertion(+) diff --git a/global.json b/global.json index b7b530d442..4628116978 100644 --- a/global.json +++ b/global.json @@ -1,4 +1,5 @@ { "sdk": { + "version": "2.1.301" } } From 60349888a6bd66424879af8477677d70057c3fb4 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 19 Jan 2019 19:45:14 +0200 Subject: [PATCH 130/178] Added test for multiple thead execution of queue. --- .../AnalyzerWorkerQueueFacts.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index e02cba8d27..c68b6e29ed 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.Extensions.Logging; @@ -130,7 +131,6 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); - queue.MarkWorkAsCompleteForProject(projectId); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); @@ -153,5 +153,33 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() Assert.True(pendingTask.IsCompleted); } + + [Fact] + public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() + { + var now = DateTime.UtcNow; + + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 50); + + var parallelQueues = + Enumerable.Range(0, 10) + .Select(_ => + Task.Run(() => { + var projectId = ProjectId.CreateNewId(); + + queue.PutWork(projectId); + + PassOverThrotlingPeriod(now); + var work = queue.TakeWork(); + + var pendingTask = queue.WaitForPendingWorkDoneEvent(work); + pendingTask.Wait(TimeSpan.FromMilliseconds(5)); + + Assert.True(pendingTask.IsCompleted); + })) + .ToArray(); + + await Task.WhenAll(parallelQueues); + } } } From 42ec450843496da3d9a302ddf1852ea77c786341 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 19 Jan 2019 20:44:13 +0200 Subject: [PATCH 131/178] Parametrized tests to test both non roslyn analysis and with analysis. --- .../DiagnosticsFacts.cs | 68 ++++++++++++------- tests/TestUtility/TestHostExtensions.cs | 4 +- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs index 20c50bc68e..330ebe2878 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; @@ -9,50 +10,65 @@ namespace OmniSharp.Roslyn.CSharp.Tests { - public class DiagnosticsFacts : AbstractSingleRequestHandlerTestFixture + public class DiagnosticsFacts { - public DiagnosticsFacts(ITestOutputHelper output, SharedOmniSharpHostFixture sharedOmniSharpHostFixture) - : base(output, sharedOmniSharpHostFixture) + private readonly ITestOutputHelper _testOutput; + + public DiagnosticsFacts(ITestOutputHelper testOutput) { + _testOutput = testOutput; } - protected override string EndpointName => OmniSharpEndpoints.CodeCheck; - [Fact] - public async Task CodeCheckSpecifiedFileOnly() + [Theory] + [InlineData("true")] + [InlineData("false")] + public async Task CodeCheckSpecifiedFileOnly(string roslynAnalyzersEnabled) { - SharedOmniSharpTestHost.AddFilesToWorkspace(new TestFile("a.cs", "class C { int n = true; }")); - var requestHandler = GetRequestHandler(SharedOmniSharpTestHost); - var quickFixes = await requestHandler.Handle(new CodeCheckRequest() { FileName = "a.cs" }); + using (var host = GetHost(roslynAnalyzersEnabled)) + { + host.AddFilesToWorkspace(new TestFile("a.cs", "class C { int n = true; }")); + var quickFixes = await host.RequestCodeCheckAsync("a.cs"); - Assert.Contains(quickFixes.QuickFixes.Select(x => x.ToString()), x => x.Contains("CS0029")); - Assert.Equal("a.cs", quickFixes.QuickFixes.First().FileName); + Assert.Contains(quickFixes.QuickFixes.Select(x => x.ToString()), x => x.Contains("CS0029")); + Assert.Equal("a.cs", quickFixes.QuickFixes.First().FileName); + } } - [Fact] - public async Task CheckAllFiles() + private OmniSharpTestHost GetHost(string roslynAnalyzersEnabled) { - var handler = GetRequestHandler(SharedOmniSharpTestHost); + return OmniSharpTestHost.Create(testOutput: _testOutput, configurationData: new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled } }); + } - SharedOmniSharpTestHost.AddFilesToWorkspace( - new TestFile("a.cs", "class C1 { int n = true; }"), - new TestFile("b.cs", "class C2 { int n = true; }")); + [Theory] + [InlineData("true")] + [InlineData("false")] + public async Task CheckAllFiles(string roslynAnalyzersEnabled) + { + using(var host = GetHost(roslynAnalyzersEnabled)) + { + host.AddFilesToWorkspace( + new TestFile("a.cs", "class C1 { int n = true; }"), + new TestFile("b.cs", "class C2 { int n = true; }")); + + var quickFixes = await host.RequestCodeCheckAsync(); - var quickFixes = await handler.Handle(new CodeCheckRequest()); - Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "a.cs"); - Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "b.cs"); + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "a.cs"); + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "b.cs"); + } } [Fact] public async Task AnalysisSupportBuiltInIDEAnalysers() { - var handler = GetRequestHandler(SharedOmniSharpTestHost); - - SharedOmniSharpTestHost.AddFilesToWorkspace( - new TestFile("a.cs", "class C1 { int n = true; }")); + using(var host = GetHost(roslynAnalyzersEnabled: "true")) + { + host.AddFilesToWorkspace( + new TestFile("a.cs", "class C1 { int n = true; }")); - var quickFixes = await handler.Handle(new CodeCheckRequest()); - Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("IDE0040")); + var quickFixes = await host.RequestCodeCheckAsync("a.cs"); + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("IDE0040")); + } } } } diff --git a/tests/TestUtility/TestHostExtensions.cs b/tests/TestUtility/TestHostExtensions.cs index 398f1bdbce..042925dcc1 100644 --- a/tests/TestUtility/TestHostExtensions.cs +++ b/tests/TestUtility/TestHostExtensions.cs @@ -30,11 +30,11 @@ public static async Task RequestMSBuildWorkspaceInfoAsync( return (MSBuildWorkspaceInfo)response["MsBuild"]; } - public static async Task RequestCodeCheckAsync(this OmniSharpTestHost host, string filePath) + public static async Task RequestCodeCheckAsync(this OmniSharpTestHost host, string filePath = null) { var service = host.GetCodeCheckService(); - var request = new CodeCheckRequest { FileName = filePath }; + var request = filePath == null ? new CodeCheckRequest() : new CodeCheckRequest { FileName = filePath }; return await service.Handle(request); } From 65c13981093d51dd83c33b07561445a68a04f1f2 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 19 Jan 2019 21:10:09 +0200 Subject: [PATCH 132/178] Attempt to remove retry from asserts. --- global.json | 1 - .../CustomRoslynAnalyzerFacts.cs | 156 ++++++++++-------- 2 files changed, 86 insertions(+), 71 deletions(-) diff --git a/global.json b/global.json index 4628116978..b7b530d442 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,4 @@ { "sdk": { - "version": "2.1.301" } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index 806446388a..0f8804a53a 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -16,10 +16,8 @@ namespace OmniSharp.Roslyn.CSharp.Tests { - public class CustomRoslynAnalyzerFacts : AbstractSingleRequestHandlerTestFixture + public class CustomRoslynAnalyzerFacts { - protected override string EndpointName => OmniSharpEndpoints.CodeCheck; - public class TestAnalyzerReference : AnalyzerReference { private readonly string _id; @@ -91,72 +89,85 @@ private void AnalyzeSymbol(SymbolAnalysisContext context) } } - public CustomRoslynAnalyzerFacts(ITestOutputHelper output, SharedOmniSharpHostFixture sharedOmniSharpHostFixture) - : base(output, sharedOmniSharpHostFixture) + private readonly ITestOutputHelper _testOutput; + + public CustomRoslynAnalyzerFacts(ITestOutputHelper testOutput) { + _testOutput = testOutput; } [Fact] public async Task When_custom_analyzers_are_executed_then_return_results() { - var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); - var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); + using (var host = GetHost()) + { + var testFile = new TestFile("testFile.cs", "class _this_is_invalid_test_class_name { int n = true; }"); - SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); + host.AddFilesToWorkspace(testFile); - var testAnalyzerRef = new TestAnalyzerReference("TS1234"); + var testAnalyzerRef = new TestAnalyzerReference("TS1234"); - var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); + AddProjectWitFile(host, testFile, testAnalyzerRef); - // This retry should be replaced with emitted events listener aproach after LSP is mainstream. - // Retry is required when build system greatly slows down some times, another issue is that - // it feels that some of update events from workspace get lost and it requires further investigation. - // If missing events are true then same issue will happen with LSP version too. - await RetryAssert.On(async () => - { - var result = await codeCheckService.Handle(new CodeCheckRequest()); + // This retry should be replaced with emitted events listener aproach after LSP is mainstream. + // Retry is required when build system greatly slows down some times, another issue is that + // it feels that some of update events from workspace get lost and it requires further investigation. + // If missing events are true then same issue will happen with LSP version too. + // await RetryAssert.On(async () => + // { + var result = await host.RequestCodeCheckAsync("testFile.cs"); Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); - }); + // }); + } + } + + private OmniSharpTestHost GetHost() + { + return OmniSharpTestHost.Create(testOutput: _testOutput); } [Fact] public async Task Always_return_results_from_net_default_analyzers() { - var testFile = new TestFile("testFile_1.cs", "class SomeClass { int n = true; }"); - var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); + using (var host = GetHost()) + { + var testFile = new TestFile("testFile_1.cs", "class SomeClass { int n = true; }"); - CreateProjectWitFile(testFile); + AddProjectWitFile(host, testFile); - await RetryAssert.On(async () => - { - var result = await codeCheckService.Handle(new CodeCheckRequest()); + // await RetryAssert.On(async () => + // { + var result = await host.RequestCodeCheckAsync(); Assert.Contains(result.QuickFixes.Where(x => x.FileName == testFile.FileName), f => f.Text.Contains("CS")); - }); + // }); + } } [Fact] public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_severity() { - var testFile = new TestFile("testFile_2.cs", "class _this_is_invalid_test_class_name { int n = true; }"); - var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - var ruleService = SharedOmniSharpTestHost.GetExport(); + using (var host = GetHost()) + { + var testFile = new TestFile("testFile_2.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var ruleService = host.GetExport(); - var testAnalyzerRef = new TestAnalyzerReference("TS1100"); + var testAnalyzerRef = new TestAnalyzerReference("TS1100"); - var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); - var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Hidden); + var projectIds = AddProjectWitFile(host, testFile, testAnalyzerRef); + var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Hidden); - ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( - "", - new ReportDiagnostic(), - testRules.ToImmutableDictionary(), - new ImmutableArray())); + ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( + "", + new ReportDiagnostic(), + testRules.ToImmutableDictionary(), + new ImmutableArray())); - await RetryAssert.On(async () => - { - var result = await codeCheckService.Handle(new CodeCheckRequest()); + // await RetryAssert.On(async () => + // { + var result = await host.RequestCodeCheckAsync("testFile_2.cs"); Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); - }); + // }); + } } private static Dictionary CreateRules(TestAnalyzerReference testAnalyzerRef, ReportDiagnostic diagnostic) @@ -171,59 +182,64 @@ private static Dictionary CreateRules(TestAnalyzerRefe // This is important because hidden still allows code fixes to execute, not prevents it, for this reason suppressed analytics should not be returned at all. public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() { - var testFile = new TestFile("testFile_3.cs", "class _this_is_invalid_test_class_name { int n = true; }"); - var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - var ruleService = SharedOmniSharpTestHost.GetExport(); + using (var host = GetHost()) + { + var testFile = new TestFile("testFile_3.cs", "class _this_is_invalid_test_class_name { int n = true; }"); - var testAnalyzerRef = new TestAnalyzerReference("TS1101"); + var ruleService = host.GetExport(); - var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); + var testAnalyzerRef = new TestAnalyzerReference("TS1101"); - var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Suppress); + var projectIds = AddProjectWitFile(host, testFile, testAnalyzerRef); - ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( - "", - new ReportDiagnostic(), - testRules.ToImmutableDictionary(), - new ImmutableArray())); + var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Suppress); - var result = await codeCheckService.Handle(new CodeCheckRequest()); - Assert.DoesNotContain(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( + "", + new ReportDiagnostic(), + testRules.ToImmutableDictionary(), + new ImmutableArray())); + + var result = await host.RequestCodeCheckAsync("testFile_3.cs"); + Assert.DoesNotContain(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); + } } [Fact] public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enable_it() { - var testFile = new TestFile("testFile_3.cs", "class _this_is_invalid_test_class_name { int n = true; }"); - var codeCheckService = GetRequestHandler(SharedOmniSharpTestHost); - var ruleService = SharedOmniSharpTestHost.GetExport(); + using (var host = GetHost()) + { + var testFile = new TestFile("testFile_4.cs", "class _this_is_invalid_test_class_name { int n = true; }"); + var ruleService = host.GetExport(); - var testAnalyzerRef = new TestAnalyzerReference("TS1101", isEnabledByDefault: false); + var testAnalyzerRef = new TestAnalyzerReference("TS1101", isEnabledByDefault: false); - var projectIds = CreateProjectWitFile(testFile, testAnalyzerRef); + var projectIds = AddProjectWitFile(host, testFile, testAnalyzerRef); - var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Error); + var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Error); - ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( - "", - new ReportDiagnostic(), - testRules.ToImmutableDictionary(), - new ImmutableArray())); + ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( + "", + new ReportDiagnostic(), + testRules.ToImmutableDictionary(), + new ImmutableArray())); - await RetryAssert.On(async () => - { - var result = await codeCheckService.Handle(new CodeCheckRequest()); + // await RetryAssert.On(async () => + // { + var result = await host.RequestCodeCheckAsync("testFile_4.cs"); Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); - }); + // }); + } } - private IEnumerable CreateProjectWitFile(TestFile testFile, TestAnalyzerReference testAnalyzerRef = null) + private IEnumerable AddProjectWitFile(OmniSharpTestHost host, TestFile testFile, TestAnalyzerReference testAnalyzerRef = null) { var analyzerReferences = testAnalyzerRef == null ? default : new AnalyzerReference[] { testAnalyzerRef }.ToImmutableArray(); return TestHelpers.AddProjectToWorkspace( - SharedOmniSharpTestHost.Workspace, + host.Workspace, "project.csproj", new[] { "netcoreapp2.1" }, new[] { testFile }, From 0384bc7ac138d9bd012534f2c199bcb360c666de Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 19 Jan 2019 21:15:24 +0200 Subject: [PATCH 133/178] Removed unused code and restored correct global.json. --- global.json | 1 + tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs | 8 ++--- .../CustomRoslynAnalyzerFacts.cs | 16 --------- tests/TestUtility/RetryAssert.cs | 33 ------------------- 4 files changed, 3 insertions(+), 55 deletions(-) delete mode 100644 tests/TestUtility/RetryAssert.cs diff --git a/global.json b/global.json index b7b530d442..4628116978 100644 --- a/global.json +++ b/global.json @@ -1,4 +1,5 @@ { "sdk": { + "version": "2.1.301" } } diff --git a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs index fa21df66ed..f6289e6e71 100644 --- a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs @@ -29,12 +29,8 @@ public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() { const string input = @"zzz"; - // Remove this retry once rare issue with missing update is handled or system is replaced with LSP event - // based approach. - await RetryAssert.On(async () => { - var diagnostics = await FindDiagnostics(input, includeFileName: true); - Assert.NotEmpty(diagnostics.QuickFixes); - }); + var diagnostics = await FindDiagnostics(input, includeFileName: true); + Assert.NotEmpty(diagnostics.QuickFixes); } [Fact] diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index 0f8804a53a..e8feadec92 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -109,15 +109,8 @@ public async Task When_custom_analyzers_are_executed_then_return_results() AddProjectWitFile(host, testFile, testAnalyzerRef); - // This retry should be replaced with emitted events listener aproach after LSP is mainstream. - // Retry is required when build system greatly slows down some times, another issue is that - // it feels that some of update events from workspace get lost and it requires further investigation. - // If missing events are true then same issue will happen with LSP version too. - // await RetryAssert.On(async () => - // { var result = await host.RequestCodeCheckAsync("testFile.cs"); Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); - // }); } } @@ -135,11 +128,8 @@ public async Task Always_return_results_from_net_default_analyzers() AddProjectWitFile(host, testFile); - // await RetryAssert.On(async () => - // { var result = await host.RequestCodeCheckAsync(); Assert.Contains(result.QuickFixes.Where(x => x.FileName == testFile.FileName), f => f.Text.Contains("CS")); - // }); } } @@ -162,11 +152,8 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ testRules.ToImmutableDictionary(), new ImmutableArray())); - // await RetryAssert.On(async () => - // { var result = await host.RequestCodeCheckAsync("testFile_2.cs"); Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains(testAnalyzerRef.Id.ToString()) && f.LogLevel == "Hidden"); - // }); } } @@ -225,11 +212,8 @@ public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enab testRules.ToImmutableDictionary(), new ImmutableArray())); - // await RetryAssert.On(async () => - // { var result = await host.RequestCodeCheckAsync("testFile_4.cs"); Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); - // }); } } diff --git a/tests/TestUtility/RetryAssert.cs b/tests/TestUtility/RetryAssert.cs deleted file mode 100644 index 0960c4ad1c..0000000000 --- a/tests/TestUtility/RetryAssert.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; - -namespace TestUtility -{ - public static class RetryAssert - { - // On 99% cases asserts should not require retry, however build systems are very slow sometimes with unpredictable ways. - public static Task On(Func action, int maxAttemptCount = 5) where TException : Exception - { - var exceptions = new List(); - - for (int attempted = 0; attempted < maxAttemptCount; attempted++) - { - try - { - if (attempted > 0) - { - Thread.Sleep(TimeSpan.FromSeconds(10)); - } - return action(); - } - catch (TException ex) - { - exceptions.Add(ex); - } - } - throw new AggregateException(exceptions); - } - } -} \ No newline at end of file From d6b8e8fee0e698b0bd5c1deea589628848451682 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 22 Jan 2019 20:36:13 +0200 Subject: [PATCH 134/178] File rename. --- .../{ICsDiagnosticService.cs => ICsDiagnosticWorker.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/{ICsDiagnosticService.cs => ICsDiagnosticWorker.cs} (100%) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticService.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs similarity index 100% rename from src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticService.cs rename to src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs From 421ef74b9900f3d6f485c384419d87707cf6e249 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 22 Jan 2019 21:38:32 +0200 Subject: [PATCH 135/178] Added test to queue facts. --- .../AnalyzerWorkerQueueFacts.cs | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index c68b6e29ed..8e07816fee 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -23,21 +23,23 @@ public IDisposable BeginScope(TState state) public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { + RecordedMessages = RecordedMessages.Add(state.ToString()); } - public ImmutableArray RecordedMessages { get; set; } + public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); } private class LoggerFactory : ILoggerFactory { - public ILogger _logger = new Logger(); + public Logger Logger { get; } = new Logger(); + public void AddProvider(ILoggerProvider provider) { } public ILogger CreateLogger(string categoryName) { - return _logger; + return Logger; } public void Dispose() @@ -140,7 +142,8 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 50); + var loggerFactory = new LoggerFactory(); + var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); var projectId = ProjectId.CreateNewId(); queue.PutWork(projectId); @@ -152,6 +155,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() pendingTask.Wait(TimeSpan.FromMilliseconds(100)); Assert.True(pendingTask.IsCompleted); + Assert.Contains("Timeout before work got ready for", loggerFactory.Logger.RecordedMessages.Single()); } [Fact] @@ -181,5 +185,36 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp await Task.WhenAll(parallelQueues); } + + [Fact] + public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() + { + var now = DateTime.UtcNow; + var loggerFactory = new LoggerFactory(); + var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); + var projectId = ProjectId.CreateNewId(); + + queue.PutWork(projectId); + + now = PassOverThrotlingPeriod(now); + + var work = queue.TakeWork(); + var waitingCall = Task.Run(async () => await queue.WaitForPendingWorkDoneEvent(work)); + await Task.Delay(50); + + // User updates code -> project is queued again during period when theres already api call waiting + // to continue. + queue.PutWork(projectId); + + // First iteration of work is done. + queue.MarkWorkAsCompleteForProject(projectId); + + // Waiting call continues because it's iteration of work is done, even when theres next + // already waiting. + await waitingCall; + + Assert.True(waitingCall.IsCompleted); + Assert.Empty(loggerFactory.Logger.RecordedMessages); + } } } From 80eb9dd264707906dcc312643f649c85c0750cce Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 27 Jan 2019 11:02:44 +0200 Subject: [PATCH 136/178] Conditional action facts with and without analyzers. --- .../CodeActionsV2Facts.cs | 97 +++++++++++++------ 1 file changed, 68 insertions(+), 29 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs index 7049a28dbc..8e3fc736ba 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs @@ -21,8 +21,10 @@ public CodeActionsV2Facts(ITestOutputHelper output) { } - [Fact] - public async Task Can_get_code_actions_from_roslyn() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_get_code_actions_from_roslyn(bool roslynAnalyzersEnabled) { const string code = @"public class Class1 @@ -33,12 +35,14 @@ public void Whatever() } }"; - var refactorings = await FindRefactoringNamesAsync(code); + var refactorings = await FindRefactoringNamesAsync(code, roslynAnalyzersEnabled); Assert.Contains("using System;", refactorings); } - [Fact] - public async Task Can_get_code_actions_from_external_source() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_get_code_actions_from_external_source(bool roslynAnalyzersEnabled) { const string code = @" @@ -55,16 +59,22 @@ public async Task Whatever() var configuration = new Dictionary { - { "RoslynExtensionsOptions:LocationPaths:0", TestAssets.Instance.TestBinariesFolder } + { "RoslynExtensionsOptions:LocationPaths:0", TestAssets.Instance.TestBinariesFolder }, }; + + GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled).ToList() + .ForEach(item => configuration.Add(item.Key, item.Value)); + var refactorings = await FindRefactoringsAsync(code, configuration); Assert.NotEmpty(refactorings); Assert.Contains("Add ConfigureAwait(false)", refactorings.Select(x => x.Name)); } - [Fact] - public async Task Can_remove_unnecessary_usings() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_remove_unnecessary_usings(bool roslynAnalyzersEnabled) { const string code = @"using MyNamespace3; @@ -80,12 +90,14 @@ public class c {public c() {Guid.NewGuid();}}"; public class c {public c() {Guid.NewGuid();}}"; - var response = await RunRefactoringAsync(code, "Remove Unnecessary Usings"); + var response = await RunRefactoringAsync(code, "Remove Unnecessary Usings", roslynAnalyzersEnabled: roslynAnalyzersEnabled); AssertIgnoringIndent(expected, ((ModifiedFileResponse)response.Changes.First()).Buffer); } - [Fact] - public async Task Can_get_ranged_code_action() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_get_ranged_code_action(bool roslynAnalyzersEnabled) { const string code = @"public class Class1 @@ -96,12 +108,14 @@ public void Whatever() } }"; - var refactorings = await FindRefactoringNamesAsync(code); + var refactorings = await FindRefactoringNamesAsync(code, roslynAnalyzersEnabled); Assert.Contains("Extract Method", refactorings); } - [Fact] - public async Task Returns_ordered_code_actions() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Returns_ordered_code_actions(bool roslynAnalyzersEnabled) { const string code = @"public class Class1 @@ -112,8 +126,9 @@ public void Whatever() } }"; - var refactorings = await FindRefactoringNamesAsync(code); - List expected = new List + var refactorings = await FindRefactoringNamesAsync(code, roslynAnalyzersEnabled); + + List expected = roslynAnalyzersEnabled ? new List { "using System;", "System.Console", @@ -126,12 +141,27 @@ public void Whatever() "Generate type 'Console' -> Generate nested class 'Console'", "Fix formatting", "Extract Method" + } : new List + { + "using System;", + "System.Console", + "Generate variable 'Console' -> Generate property 'Class1.Console'", + "Generate variable 'Console' -> Generate field 'Class1.Console'", + "Generate variable 'Console' -> Generate read-only field 'Class1.Console'", + "Generate variable 'Console' -> Generate local 'Console'", + "Generate type 'Console' -> Generate class 'Console' in new file", + "Generate type 'Console' -> Generate class 'Console'", + "Generate type 'Console' -> Generate nested class 'Console'", + "Extract Method" }; + Assert.Equal(expected, refactorings); } - [Fact] - public async Task Can_extract_method() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_extract_method(bool roslynAnalyzersEnabled) { const string code = @"public class Class1 @@ -154,15 +184,17 @@ private static void NewMethod() Console.Write(""should be using System;""); } }"; - var response = await RunRefactoringAsync(code, "Extract Method"); + var response = await RunRefactoringAsync(code, "Extract Method", roslynAnalyzersEnabled: roslynAnalyzersEnabled); AssertIgnoringIndent(expected, ((ModifiedFileResponse)response.Changes.First()).Buffer); } - [Fact] - public async Task Can_generate_type_and_return_name_of_new_file() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_generate_type_and_return_name_of_new_file(bool roslynAnalyzersEnabled) { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectWithMissingType")) - using (var host = CreateOmniSharpHost(testProject.Directory)) + using (var host = CreateOmniSharpHost(testProject.Directory, configurationData: GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled))) { var requestHandler = host.GetRequestHandler(OmniSharpEndpoints.V2.RunCodeAction); var document = host.Workspace.CurrentSolution.Projects.First().Documents.First(); @@ -197,11 +229,13 @@ internal class Z } } - [Fact] - public async Task Can_send_rename_and_fileOpen_responses_when_codeAction_renames_file() + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Can_send_rename_and_fileOpen_responses_when_codeAction_renames_file(bool roslynAnalyzersEnabled) { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectWithMismatchedFileName")) - using (var host = CreateOmniSharpHost(testProject.Directory)) + using (var host = CreateOmniSharpHost(testProject.Directory, configurationData: GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled))) { var requestHandler = host.GetRequestHandler(OmniSharpEndpoints.V2.RunCodeAction); var document = host.Workspace.CurrentSolution.Projects.First().Documents.First(); @@ -238,22 +272,27 @@ private static string TrimLines(string source) return string.Join("\n", source.Split('\n').Select(s => s.Trim())); } - private async Task RunRefactoringAsync(string code, string refactoringName, bool wantsChanges = false) + private async Task RunRefactoringAsync(string code, string refactoringName, bool wantsChanges = false, bool roslynAnalyzersEnabled = false) { - var refactorings = await FindRefactoringsAsync(code); + var refactorings = await FindRefactoringsAsync(code, configurationData: GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled)); Assert.Contains(refactoringName, refactorings.Select(a => a.Name)); var identifier = refactorings.First(action => action.Name.Equals(refactoringName)).Identifier; return await RunRefactoringsAsync(code, identifier, wantsChanges); } - private async Task> FindRefactoringNamesAsync(string code) + private async Task> FindRefactoringNamesAsync(string code, bool roslynAnalyzersEnabled = false) { - var codeActions = await FindRefactoringsAsync(code); + var codeActions = await FindRefactoringsAsync(code, configurationData: GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled)); return codeActions.Select(a => a.Name); } + private Dictionary GetConfigurationDataWithAnalyzerConfig(bool roslynAnalyzersEnabled) + { + return new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString() } }; + } + private async Task> FindRefactoringsAsync(string code, IDictionary configurationData = null) { var testFile = new TestFile(BufferPath, code); From 1ee73727c8a89c4ae72f6a61fe39e5a68685f463 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 27 Jan 2019 11:23:47 +0200 Subject: [PATCH 137/178] Small refactoring. --- .../DiagnosticsFacts.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs index 330ebe2878..9dd2896d54 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs @@ -21,9 +21,9 @@ public DiagnosticsFacts(ITestOutputHelper testOutput) [Theory] - [InlineData("true")] - [InlineData("false")] - public async Task CodeCheckSpecifiedFileOnly(string roslynAnalyzersEnabled) + [InlineData(true)] + [InlineData(false)] + public async Task CodeCheckSpecifiedFileOnly(bool roslynAnalyzersEnabled) { using (var host = GetHost(roslynAnalyzersEnabled)) { @@ -35,15 +35,15 @@ public async Task CodeCheckSpecifiedFileOnly(string roslynAnalyzersEnabled) } } - private OmniSharpTestHost GetHost(string roslynAnalyzersEnabled) + private OmniSharpTestHost GetHost(bool roslynAnalyzersEnabled) { - return OmniSharpTestHost.Create(testOutput: _testOutput, configurationData: new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled } }); + return OmniSharpTestHost.Create(testOutput: _testOutput, configurationData: new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString() } }); } [Theory] - [InlineData("true")] - [InlineData("false")] - public async Task CheckAllFiles(string roslynAnalyzersEnabled) + [InlineData(true)] + [InlineData(false)] + public async Task CheckAllFiles(bool roslynAnalyzersEnabled) { using(var host = GetHost(roslynAnalyzersEnabled)) { @@ -61,7 +61,7 @@ public async Task CheckAllFiles(string roslynAnalyzersEnabled) [Fact] public async Task AnalysisSupportBuiltInIDEAnalysers() { - using(var host = GetHost(roslynAnalyzersEnabled: "true")) + using(var host = GetHost(roslynAnalyzersEnabled: true)) { host.AddFilesToWorkspace( new TestFile("a.cs", "class C1 { int n = true; }")); From e423278345eb68f733c40db8ec1009775ce95f0b Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 9 Feb 2019 10:15:20 +0200 Subject: [PATCH 138/178] Fix for lsp merge. --- .../Workers/Diagnostics/CSharpDiagnosticWorker.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 9e69187bdd..7e10bb7e86 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -67,12 +67,6 @@ private void OnDocumentOpened(object sender, DocumentEventArgs args) } } - private void EmitDiagnostics(params string[] documents) - { - EmitDiagnostics(args.Document.FilePath); - EmitDiagnostics(_workspace.GetOpenDocumentIds().Select(x => _workspace.CurrentSolution.GetDocument(x).FilePath).ToArray()); - } - private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { if (!_forwarder.IsEnabled) @@ -84,7 +78,6 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv { var newDocument = changeEvent.NewSolution.GetDocument(changeEvent.DocumentId); - EmitDiagnostics(newDocument.FilePath); EmitDiagnostics(_workspace.GetOpenDocumentIds().Select(x => _workspace.CurrentSolution.GetDocument(x).FilePath).ToArray()); } else if (changeEvent.Kind == WorkspaceChangeKind.ProjectAdded || changeEvent.Kind == WorkspaceChangeKind.ProjectReloaded) From 5e11541871a74a9c7b6981a6456d9631111e2a2a Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 14 Feb 2019 21:07:17 +0200 Subject: [PATCH 139/178] Initial version of single file analysis. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 36 +++--- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 120 +++++++----------- .../AnalyzerWorkerQueueFacts.cs | 63 +++++---- .../CodeActionsV2Facts.cs | 15 +-- .../DiagnosticsFacts.cs | 2 +- 5 files changed, 103 insertions(+), 133 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 6ac8b15cae..eeb13e2a33 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -13,14 +13,14 @@ public class AnalyzerWorkQueue { private readonly int _throttlingMs = 300; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); - private readonly ConcurrentDictionary _currentWork = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _currentWork = + new ConcurrentDictionary(); private readonly Func _utcNow; private readonly int _timeoutForPendingWorkMs; - private ILogger _logger; + private readonly ILogger _logger; public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 60*1000) { @@ -32,14 +32,14 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = n _timeoutForPendingWorkMs = timeoutForPendingWorkMs; } - public void PutWork(ProjectId projectId) + public void PutWork(Document document) { - _workQueue.AddOrUpdate(projectId, - (modified: DateTime.UtcNow, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, oldValue.workDoneSource)); + _workQueue.AddOrUpdate(document.Id, + (modified: DateTime.UtcNow, document, new CancellationTokenSource()), + (_, oldValue) => (modified: DateTime.UtcNow, document, oldValue.workDoneSource)); } - public ImmutableArray TakeWork() + public ImmutableArray TakeWork() { lock (_workQueue) { @@ -47,7 +47,7 @@ public ImmutableArray TakeWork() var currentWork = _workQueue .Where(x => ThrottlingPeriodNotActive(x.Value.modified, now)) .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. - .Take(1) // Limit amount of work executed by once. This is needed on large solution... + .Take(30) // Limit amount of work executed by once. This is needed on large solution... .ToImmutableArray(); foreach (var work in currentWork) @@ -56,7 +56,7 @@ public ImmutableArray TakeWork() _currentWork.TryAdd(work.Key, work.Value); } - return currentWork.Select(x => x.Key).ToImmutableArray(); + return currentWork.Select(x => x.Value.document).ToImmutableArray(); } } @@ -65,24 +65,24 @@ private bool ThrottlingPeriodNotActive(DateTime modified, DateTime now) return (now - modified).TotalMilliseconds >= _throttlingMs; } - public void MarkWorkAsCompleteForProject(ProjectId projectId) + public void MarkWorkAsCompleteForDocument(Document document) { - if(_currentWork.TryGetValue(projectId, out var work)) + if(_currentWork.TryGetValue(document.Id, out var work)) { work.workDoneSource.Cancel(); - _currentWork.TryRemove(projectId, out _); + _currentWork.TryRemove(document.Id, out _); } } // Omnisharp V2 api expects that it can request current information of diagnostics any time, // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. - public async Task WaitForPendingWorkDoneEvent(ImmutableArray projectIds) + public async Task WaitForPendingWorkDoneEvent(ImmutableArray documents) { - var currentWorkMatches = _currentWork.Where(x => projectIds.Any(pid => pid == x.Key)); + var currentWorkMatches = _currentWork.Where(x => documents.Any(doc => doc.Id == x.Key)); var pendingWorkThatDoesntExistInCurrentWork = _workQueue - .Where(x => projectIds.Any(pid => pid == x.Key)) + .Where(x => documents.Any(doc => doc.Id == x.Key)) .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); await Task.WhenAll( diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index a7b673dfc6..9073a83dbf 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -23,8 +23,8 @@ public class CSharpDiagnosticWorkerWithAnalyzers: ICsDiagnosticWorker { private readonly AnalyzerWorkQueue _workQueue; private readonly ILogger _logger; - private readonly ConcurrentDictionary diagnostics)> _results = - new ConcurrentDictionary diagnostics)>(); + private readonly ConcurrentDictionary diagnostics)> _results = + new ConcurrentDictionary diagnostics)>(); private readonly ImmutableArray _providers; private readonly DiagnosticEventForwarder _forwarder; private readonly OmniSharpWorkspace _workspace; @@ -61,7 +61,7 @@ public CSharpDiagnosticWorkerWithAnalyzers( Task.Run(async () => { while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(200); - QueueForAnalysis(workspace.CurrentSolution.Projects.Select(x => x.Id).ToImmutableArray()); + QueueForAnalysis(workspace.CurrentSolution.Projects.SelectMany(x => x.Documents).ToImmutableArray()); _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); }); @@ -70,18 +70,16 @@ public CSharpDiagnosticWorkerWithAnalyzers( public void QueueForDiagnosis(ImmutableArray documents) { - QueueForAnalysis(GetProjectIdsFromDocuments(documents)); + QueueForAnalysis(documents); } public async Task> GetDiagnostics(ImmutableArray documents) { - var projectIds = GetProjectIdsFromDocuments(documents); - - await _workQueue.WaitForPendingWorkDoneEvent(projectIds); + await _workQueue.WaitForPendingWorkDoneEvent(documents); return _results - .Where(x => projectIds.Any(pid => pid == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.name, v))) + .Where(x => documents.Any(doc => doc.Id == x.Key)) + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.projectName, v))) .ToImmutableArray(); } @@ -91,8 +89,12 @@ private async Task Worker() { try { - var currentWork = TakeNextBatchOfProjectsForAnalysisIfAny(); - await Task.WhenAll(currentWork.Select(x => Analyze(x))); + var currentWorkGroupedByProjects = _workQueue + .TakeWork() + .GroupBy(x => x.Project) + .ToImmutableArray(); + + await Task.WhenAll(currentWorkGroupedByProjects.Select(x => Analyze(x.Key, x.ToImmutableArray()))); await Task.Delay(100); } catch (Exception ex) @@ -102,19 +104,11 @@ private async Task Worker() } } - private ImmutableArray TakeNextBatchOfProjectsForAnalysisIfAny() - { - return _workQueue.TakeWork() - .Select(projectId => _workspace?.CurrentSolution?.GetProject(projectId)) - .Where(project => project != null) // This may occur if project removed middle of analysis from solution. - .ToImmutableArray(); - } - - private void QueueForAnalysis(ImmutableArray projects) + private void QueueForAnalysis(ImmutableArray documents) { - foreach (var projectId in projects) + foreach (var document in documents) { - _workQueue.PutWork(projectId); + _workQueue.PutWork(document); } } @@ -122,25 +116,16 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv { if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged || changeEvent.Kind == WorkspaceChangeKind.DocumentRemoved - || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded - || changeEvent.Kind == WorkspaceChangeKind.ProjectAdded) + || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded) { - QueueForAnalysis(ImmutableArray.Create(changeEvent.ProjectId)); + QueueForAnalysis(ImmutableArray.Create(_workspace.CurrentSolution.GetDocument(changeEvent.DocumentId))); } } - private async Task Analyze(Project project) + private async Task Analyze(Project project, ImmutableArray projectDocuments) { try { - // Only basic syntax check is available if file is miscellanous like orphan .cs file. - // Todo: Where this magic string should be moved? - if (project.Name == "MiscellaneousFiles.csproj") - { - await AnalyzeSingleMiscFilesProject(project); - return; - } - var allAnalyzers = _providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language))) @@ -150,34 +135,39 @@ private async Task Analyze(Project project) _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) .GetCompilationAsync(); - ImmutableArray results = ImmutableArray.Empty; - - if (allAnalyzers.Any()) + foreach(var document in projectDocuments) { - var workspaceAnalyzerOptions = - (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution.Options, project.Solution }); - - results = await compiled - .WithAnalyzers(allAnalyzers, workspaceAnalyzerOptions) // This cannot be invoked with empty analyzers list. - .GetAllDiagnosticsAsync(); + // Only basic syntax check is available if file is miscellanous like orphan .cs file. + if (allAnalyzers.Any() && project.Name != "MiscellaneousFiles.csproj") + { + var workspaceAnalyzerOptions = + (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution.Options, project.Solution }); + + var documentSemanticModel = await document.GetSemanticModelAsync(); + + var diagnosticsWithAnalyzers = await compiled + .WithAnalyzers(allAnalyzers, workspaceAnalyzerOptions) + .GetAnalyzerSemanticDiagnosticsAsync(documentSemanticModel, filterSpan: null, CancellationToken.None); // This cannot be invoked with empty analyzers list. + + UpdateCurrentDiagnostics(project, document, diagnosticsWithAnalyzers.Concat(documentSemanticModel.GetDiagnostics()).ToImmutableArray()); + } + else + { + UpdateCurrentDiagnostics(project, document, compiled.GetDiagnostics()); + } } - else - { - results = compiled.GetDiagnostics(); - } - - _results[project.Id] = (project.Name, results); - - EmitDiagnostics(results); } catch (Exception ex) { _logger.LogError($"Analysis of project {project.Id} ({project.Name}) failed, underlaying error: {ex}"); } - finally - { - _workQueue.MarkWorkAsCompleteForProject(project.Id); - } + } + + private void UpdateCurrentDiagnostics(Project project, Document document, ImmutableArray diagnosticsWithAnalyzers) + { + _results[document.Id] = (project.Name, diagnosticsWithAnalyzers); + _workQueue.MarkWorkAsCompleteForDocument(document); + EmitDiagnostics(_results[document.Id].diagnostics); } private void EmitDiagnostics(ImmutableArray results) @@ -194,25 +184,5 @@ private void EmitDiagnostics(ImmutableArray results) }); } } - - private async Task AnalyzeSingleMiscFilesProject(Project project) - { - var syntaxTrees = await Task.WhenAll(project.Documents - .Select(async document => await document.GetSyntaxTreeAsync())); - - var results = syntaxTrees - .Select(x => x.GetDiagnostics()) - .SelectMany(x => x); - - _results[project.Id] = (project.Name, results.ToImmutableArray()); - } - - private static ImmutableArray GetProjectIdsFromDocuments(ImmutableArray documents) - { - return documents - .Select(x => x.Project?.Id) - .Where(x => x != null) - .ToImmutableArray(); - } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 8e07816fee..8d6c2e3363 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; @@ -52,9 +53,9 @@ public void WhenProjectsAreAddedButThrotlingIsntOverNoProjectsShouldBeReturned() { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var projectId = ProjectId.CreateNewId(); + var document = CreateTestDocument(); - queue.PutWork(projectId); + queue.PutWork(document); Assert.Empty(queue.TakeWork()); } @@ -63,13 +64,13 @@ public void WhenProjectsAreAddedToQueueThenTheyWillBeReturned() { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var projectId = ProjectId.CreateNewId(); + var document = CreateTestDocument(); - queue.PutWork(projectId); + queue.PutWork(document); now = PassOverThrotlingPeriod(now); - Assert.Contains(projectId, queue.TakeWork()); + Assert.Contains(document, queue.TakeWork()); Assert.Empty(queue.TakeWork()); } @@ -78,17 +79,17 @@ public void WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAsOne() { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var projectId = ProjectId.CreateNewId(); + var document = CreateTestDocument(); - queue.PutWork(projectId); - queue.PutWork(projectId); - queue.PutWork(projectId); + queue.PutWork(document); + queue.PutWork(document); + queue.PutWork(document); Assert.Empty(queue.TakeWork()); now = PassOverThrotlingPeriod(now); - Assert.Contains(projectId, queue.TakeWork()); + Assert.Contains(document, queue.TakeWork()); Assert.Empty(queue.TakeWork()); } @@ -99,11 +100,11 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); - var projectId = ProjectId.CreateNewId(); + var document = CreateTestDocument(); - queue.PutWork(projectId); + queue.PutWork(document); - var pendingTask = queue.WaitForPendingWorkDoneEvent(new [] { projectId }.ToImmutableArray()); + var pendingTask = queue.WaitForPendingWorkDoneEvent(new [] { document }.ToImmutableArray()); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -111,7 +112,7 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - queue.MarkWorkAsCompleteForProject(projectId); + queue.MarkWorkAsCompleteForDocument(document); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -121,9 +122,9 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); - var projectId = ProjectId.CreateNewId(); + var document = CreateTestDocument(); - queue.PutWork(projectId); + queue.PutWork(document); now = PassOverThrotlingPeriod(now); @@ -133,7 +134,7 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); - queue.MarkWorkAsCompleteForProject(projectId); + queue.MarkWorkAsCompleteForDocument(document); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -144,9 +145,9 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() var now = DateTime.UtcNow; var loggerFactory = new LoggerFactory(); var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); - var projectId = ProjectId.CreateNewId(); + var document = CreateTestDocument(); - queue.PutWork(projectId); + queue.PutWork(document); now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); @@ -169,9 +170,9 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp Enumerable.Range(0, 10) .Select(_ => Task.Run(() => { - var projectId = ProjectId.CreateNewId(); + var document = CreateTestDocument(); - queue.PutWork(projectId); + queue.PutWork(document); PassOverThrotlingPeriod(now); var work = queue.TakeWork(); @@ -192,9 +193,9 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe var now = DateTime.UtcNow; var loggerFactory = new LoggerFactory(); var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); - var projectId = ProjectId.CreateNewId(); + var document = CreateTestDocument(); - queue.PutWork(projectId); + queue.PutWork(document); now = PassOverThrotlingPeriod(now); @@ -204,10 +205,10 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe // User updates code -> project is queued again during period when theres already api call waiting // to continue. - queue.PutWork(projectId); + queue.PutWork(document); // First iteration of work is done. - queue.MarkWorkAsCompleteForProject(projectId); + queue.MarkWorkAsCompleteForDocument(document); // Waiting call continues because it's iteration of work is done, even when theres next // already waiting. @@ -216,5 +217,17 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe Assert.True(waitingCall.IsCompleted); Assert.Empty(loggerFactory.Logger.RecordedMessages); } + + private Document CreateTestDocument() + { + var tempWorkspace = new AdhocWorkspace(); + return tempWorkspace.AddDocument(DocumentInfo.Create( + id: DocumentId.CreateNewId(ProjectId.CreateNewId()), + name: "testFile.cs", + sourceCodeKind: SourceCodeKind.Regular, + loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create())), + filePath: @"c:\testFile.cs")); + + } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs index 8e3fc736ba..f3d509722a 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs @@ -128,20 +128,7 @@ public void Whatever() var refactorings = await FindRefactoringNamesAsync(code, roslynAnalyzersEnabled); - List expected = roslynAnalyzersEnabled ? new List - { - "using System;", - "System.Console", - "Generate variable 'Console' -> Generate property 'Class1.Console'", - "Generate variable 'Console' -> Generate field 'Class1.Console'", - "Generate variable 'Console' -> Generate read-only field 'Class1.Console'", - "Generate variable 'Console' -> Generate local 'Console'", - "Generate type 'Console' -> Generate class 'Console' in new file", - "Generate type 'Console' -> Generate class 'Console'", - "Generate type 'Console' -> Generate nested class 'Console'", - "Fix formatting", - "Extract Method" - } : new List + List expected = new List { "using System;", "System.Console", diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs index 9dd2896d54..8806a476e9 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs @@ -67,7 +67,7 @@ public async Task AnalysisSupportBuiltInIDEAnalysers() new TestFile("a.cs", "class C1 { int n = true; }")); var quickFixes = await host.RequestCodeCheckAsync("a.cs"); - Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("IDE0040")); + Assert.Contains(quickFixes.QuickFixes, x => x.Text.Contains("IDE0044")); } } } From a3e11d0454545205495157253a62882ac4c98018 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 13:17:42 +0200 Subject: [PATCH 140/178] Fixes and tweaks for analysis, timeouts and error handling. --- global.json | 1 - .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 44 +- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 76 ++-- .../CodeActionsV2Facts.cs | 1 - .../AnalyzerWorkerQueueFacts.cs | 400 +++++++++--------- 5 files changed, 272 insertions(+), 250 deletions(-) diff --git a/global.json b/global.json index 4628116978..b7b530d442 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,4 @@ { "sdk": { - "version": "2.1.301" } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index eeb13e2a33..f8f64618cc 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -13,16 +13,16 @@ public class AnalyzerWorkQueue { private readonly int _throttlingMs = 300; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); - private readonly ConcurrentDictionary _currentWork = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _currentWork = + new ConcurrentDictionary(); private readonly Func _utcNow; private readonly int _timeoutForPendingWorkMs; private readonly ILogger _logger; - public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 60*1000) + public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 15*1000) { if(utcNow == null) utcNow = () => DateTime.UtcNow; @@ -35,8 +35,8 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = n public void PutWork(Document document) { _workQueue.AddOrUpdate(document.Id, - (modified: DateTime.UtcNow, document, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, document, oldValue.workDoneSource)); + (modified: DateTime.UtcNow, document, new ManualResetEvent(false)), + (_, oldValue) => (modified: DateTime.UtcNow, document, oldValue.manualResetEvent)); } public ImmutableArray TakeWork() @@ -46,8 +46,6 @@ public ImmutableArray TakeWork() var now = _utcNow(); var currentWork = _workQueue .Where(x => ThrottlingPeriodNotActive(x.Value.modified, now)) - .OrderByDescending(x => x.Value.modified) // If you currently edit project X you want it will be highest priority and contains always latest possible analysis. - .Take(30) // Limit amount of work executed by once. This is needed on large solution... .ToImmutableArray(); foreach (var work in currentWork) @@ -69,15 +67,15 @@ public void MarkWorkAsCompleteForDocument(Document document) { if(_currentWork.TryGetValue(document.Id, out var work)) { - work.workDoneSource.Cancel(); _currentWork.TryRemove(document.Id, out _); + work.manualResetEvent.Set(); } } // Omnisharp V2 api expects that it can request current information of diagnostics any time, // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. - public async Task WaitForPendingWorkDoneEvent(ImmutableArray documents) + public void WaitForPendingWorkDoneEvent(ImmutableArray documents) { var currentWorkMatches = _currentWork.Where(x => documents.Any(doc => doc.Id == x.Key)); @@ -85,18 +83,20 @@ public async Task WaitForPendingWorkDoneEvent(ImmutableArray documents .Where(x => documents.Any(doc => doc.Id == x.Key)) .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); - await Task.WhenAll( - currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork) - .Select(x => Task.Delay(_timeoutForPendingWorkMs, x.Value.workDoneSource.Token) - .ContinueWith(task => LogTimeouts(task, x.Key.ToString()))) - .ToImmutableArray()); - } + // Not perfect but WaitAll only accepts up to 64 handles at once. + var workToWait = currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork).Take(60); - // This is basically asserting mechanism for hanging analysis if any. If this doesn't exist tracking - // down why results doesn't come up (for example in situation when theres bad analyzer that takes ages to complete). - private void LogTimeouts(Task task, string description) - { - if (!task.IsCanceled) _logger.LogError($"Timeout before work got ready for {description}."); + if(workToWait.Any()) + { + var waitComplete = WaitHandle.WaitAll( + workToWait.Select(x => x.Value.manualResetEvent).ToArray(), + _timeoutForPendingWorkMs); + + if(!waitComplete) + { + _logger.LogError($"Timeout before work got ready. Documents waited {String.Join(",", workToWait.Select(x => x.Value.document.Name))}."); + } + } } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 9073a83dbf..49bc515f59 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -19,7 +19,7 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics { - public class CSharpDiagnosticWorkerWithAnalyzers: ICsDiagnosticWorker + public class CSharpDiagnosticWorkerWithAnalyzers : ICsDiagnosticWorker { private readonly AnalyzerWorkQueue _workQueue; private readonly ILogger _logger; @@ -73,14 +73,17 @@ public void QueueForDiagnosis(ImmutableArray documents) QueueForAnalysis(documents); } - public async Task> GetDiagnostics(ImmutableArray documents) + public Task> GetDiagnostics(ImmutableArray documents) { - await _workQueue.WaitForPendingWorkDoneEvent(documents); + return Task.Run(() => + { + _workQueue.WaitForPendingWorkDoneEvent(documents); - return _results - .Where(x => documents.Any(doc => doc.Id == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.projectName, v))) - .ToImmutableArray(); + return _results + .Where(x => documents.Any(doc => doc.Id == x.Key)) + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.projectName, v))) + .ToImmutableArray(); + }); } private async Task Worker() @@ -94,8 +97,12 @@ private async Task Worker() .GroupBy(x => x.Project) .ToImmutableArray(); - await Task.WhenAll(currentWorkGroupedByProjects.Select(x => Analyze(x.Key, x.ToImmutableArray()))); - await Task.Delay(100); + foreach (var projectGroup in currentWorkGroupedByProjects) + { + await Analyze(projectGroup.Key, projectGroup.ToImmutableArray()); + } + + await Task.Delay(50); } catch (Exception ex) { @@ -135,31 +142,48 @@ private async Task Analyze(Project project, ImmutableArray projectDocu _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) .GetCompilationAsync(); - foreach(var document in projectDocuments) + var workspaceAnalyzerOptions = + (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution.Options, project.Solution }); + + foreach (var document in projectDocuments) { - // Only basic syntax check is available if file is miscellanous like orphan .cs file. - if (allAnalyzers.Any() && project.Name != "MiscellaneousFiles.csproj") - { - var workspaceAnalyzerOptions = - (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution.Options, project.Solution }); + await AnalyzeDocument(project, allAnalyzers, compiled, workspaceAnalyzerOptions, document); + } + } + catch (Exception ex) + { + _logger.LogError($"Analysis of project {project.Id} ({project.Name}) failed, underlaying error: {ex}"); + } + } - var documentSemanticModel = await document.GetSemanticModelAsync(); + private async Task AnalyzeDocument(Project project, ImmutableArray allAnalyzers, Compilation compiled, AnalyzerOptions workspaceAnalyzerOptions, Document document) + { + try + { + // Only basic syntax check is available if file is miscellanous like orphan .cs file. + // Those projects are on hard coded virtual project named 'MiscellaneousFiles.csproj'. + if (allAnalyzers.Any() && project.Name != "MiscellaneousFiles.csproj") + { + // Theres real possibility that bug in analyzer causes analysis hang or end to infinite loop. + var perDocumentTimeout = new CancellationTokenSource(10 * 1000); - var diagnosticsWithAnalyzers = await compiled - .WithAnalyzers(allAnalyzers, workspaceAnalyzerOptions) - .GetAnalyzerSemanticDiagnosticsAsync(documentSemanticModel, filterSpan: null, CancellationToken.None); // This cannot be invoked with empty analyzers list. + var documentSemanticModel = await document.GetSemanticModelAsync(perDocumentTimeout.Token); - UpdateCurrentDiagnostics(project, document, diagnosticsWithAnalyzers.Concat(documentSemanticModel.GetDiagnostics()).ToImmutableArray()); - } - else - { - UpdateCurrentDiagnostics(project, document, compiled.GetDiagnostics()); - } + var diagnosticsWithAnalyzers = await compiled + .WithAnalyzers(allAnalyzers, workspaceAnalyzerOptions) + .GetAnalyzerSemanticDiagnosticsAsync(documentSemanticModel, filterSpan: null, perDocumentTimeout.Token); // This cannot be invoked with empty analyzers list. + + UpdateCurrentDiagnostics(project, document, diagnosticsWithAnalyzers.Concat(documentSemanticModel.GetDiagnostics()).ToImmutableArray()); + } + else + { + UpdateCurrentDiagnostics(project, document, compiled.GetDiagnostics()); } } catch (Exception ex) { - _logger.LogError($"Analysis of project {project.Id} ({project.Name}) failed, underlaying error: {ex}"); + _logger.LogError($"Analysis of document {document.Name} failed or cancelled by timeout: {ex.Message}"); + _workQueue.MarkWorkAsCompleteForDocument(document); } } diff --git a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs index def1c492cc..6a8987e24e 100644 --- a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs @@ -64,7 +64,6 @@ public void Whatever() { "using System.Text.RegularExpressions;", "System.Text.RegularExpressions.Regex", - "Fix formatting", "Extract Method" }; Assert.Equal(expected, refactorings); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 8d6c2e3363..2af817213d 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -1,233 +1,233 @@ -using System; -using System.Collections.Immutable; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; -using Xunit; - -namespace OmniSharp.Roslyn.CSharp.Tests -{ - public class AnalyzerWorkerQueueFacts - { - private class Logger : ILogger - { - public IDisposable BeginScope(TState state) - { - return null; - } - - public bool IsEnabled(LogLevel logLevel) => true; - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) - { - RecordedMessages = RecordedMessages.Add(state.ToString()); - } - - public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); - } - - private class LoggerFactory : ILoggerFactory - { - public Logger Logger { get; } = new Logger(); - - public void AddProvider(ILoggerProvider provider) - { - } - - public ILogger CreateLogger(string categoryName) - { - return Logger; - } - - public void Dispose() - { - } - } - - [Fact] - public void WhenProjectsAreAddedButThrotlingIsntOverNoProjectsShouldBeReturned() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocument(); - - queue.PutWork(document); - Assert.Empty(queue.TakeWork()); - } - - [Fact] - public void WhenProjectsAreAddedToQueueThenTheyWillBeReturned() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocument(); - - queue.PutWork(document); - - now = PassOverThrotlingPeriod(now); - - Assert.Contains(document, queue.TakeWork()); - Assert.Empty(queue.TakeWork()); - } - - [Fact] - public void WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAsOne() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocument(); - - queue.PutWork(document); - queue.PutWork(document); - queue.PutWork(document); - - Assert.Empty(queue.TakeWork()); - - now = PassOverThrotlingPeriod(now); - - Assert.Contains(document, queue.TakeWork()); - Assert.Empty(queue.TakeWork()); - } - - private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(1); - - [Fact] - public void WhenWorkIsAddedThenWaitNextIterationOfItReady() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); - var document = CreateTestDocument(); - - queue.PutWork(document); +// using System; +// using System.Collections.Immutable; +// using System.Linq; +// using System.Threading.Tasks; +// using Microsoft.CodeAnalysis; +// using Microsoft.CodeAnalysis.Text; +// using Microsoft.Extensions.Logging; +// using Microsoft.Extensions.Logging.Abstractions; +// using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; +// using Xunit; + +// namespace OmniSharp.Roslyn.CSharp.Tests +// { +// public class AnalyzerWorkerQueueFacts +// { +// private class Logger : ILogger +// { +// public IDisposable BeginScope(TState state) +// { +// return null; +// } + +// public bool IsEnabled(LogLevel logLevel) => true; + +// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) +// { +// RecordedMessages = RecordedMessages.Add(state.ToString()); +// } + +// public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); +// } + +// private class LoggerFactory : ILoggerFactory +// { +// public Logger Logger { get; } = new Logger(); + +// public void AddProvider(ILoggerProvider provider) +// { +// } + +// public ILogger CreateLogger(string categoryName) +// { +// return Logger; +// } + +// public void Dispose() +// { +// } +// } + +// [Fact] +// public void WhenProjectsAreAddedButThrotlingIsntOverNoProjectsShouldBeReturned() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); +// var document = CreateTestDocument(); + +// queue.PutWork(document); +// Assert.Empty(queue.TakeWork()); +// } + +// [Fact] +// public void WhenProjectsAreAddedToQueueThenTheyWillBeReturned() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); +// var document = CreateTestDocument(); + +// queue.PutWork(document); + +// now = PassOverThrotlingPeriod(now); + +// Assert.Contains(document, queue.TakeWork()); +// Assert.Empty(queue.TakeWork()); +// } + +// [Fact] +// public void WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAsOne() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); +// var document = CreateTestDocument(); + +// queue.PutWork(document); +// queue.PutWork(document); +// queue.PutWork(document); + +// Assert.Empty(queue.TakeWork()); + +// now = PassOverThrotlingPeriod(now); + +// Assert.Contains(document, queue.TakeWork()); +// Assert.Empty(queue.TakeWork()); +// } + +// private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(1); + +// [Fact] +// public void WhenWorkIsAddedThenWaitNextIterationOfItReady() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); +// var document = CreateTestDocument(); + +// queue.PutWork(document); - var pendingTask = queue.WaitForPendingWorkDoneEvent(new [] { document }.ToImmutableArray()); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +// var pendingTask = queue.WaitForPendingWorkDoneEvent(new [] { document }.ToImmutableArray()); +// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); - Assert.False(pendingTask.IsCompleted); +// Assert.False(pendingTask.IsCompleted); - now = PassOverThrotlingPeriod(now); +// now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); - queue.MarkWorkAsCompleteForDocument(document); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); - Assert.True(pendingTask.IsCompleted); - } +// var work = queue.TakeWork(); +// queue.MarkWorkAsCompleteForDocument(document); +// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +// Assert.True(pendingTask.IsCompleted); +// } - [Fact] - public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() - { - var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); - var document = CreateTestDocument(); +// [Fact] +// public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() +// { +// var now = DateTime.UtcNow; +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); +// var document = CreateTestDocument(); - queue.PutWork(document); +// queue.PutWork(document); - now = PassOverThrotlingPeriod(now); +// now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); +// var work = queue.TakeWork(); - var pendingTask = queue.WaitForPendingWorkDoneEvent(work); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +// var pendingTask = queue.WaitForPendingWorkDoneEvent(work); +// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); - Assert.False(pendingTask.IsCompleted); - queue.MarkWorkAsCompleteForDocument(document); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); - Assert.True(pendingTask.IsCompleted); - } +// Assert.False(pendingTask.IsCompleted); +// queue.MarkWorkAsCompleteForDocument(document); +// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); +// Assert.True(pendingTask.IsCompleted); +// } - [Fact] - public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() - { - var now = DateTime.UtcNow; - var loggerFactory = new LoggerFactory(); - var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); - var document = CreateTestDocument(); +// [Fact] +// public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() +// { +// var now = DateTime.UtcNow; +// var loggerFactory = new LoggerFactory(); +// var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); +// var document = CreateTestDocument(); - queue.PutWork(document); +// queue.PutWork(document); - now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); +// now = PassOverThrotlingPeriod(now); +// var work = queue.TakeWork(); - var pendingTask = queue.WaitForPendingWorkDoneEvent(work); - pendingTask.Wait(TimeSpan.FromMilliseconds(100)); +// var pendingTask = queue.WaitForPendingWorkDoneEvent(work); +// pendingTask.Wait(TimeSpan.FromMilliseconds(100)); - Assert.True(pendingTask.IsCompleted); - Assert.Contains("Timeout before work got ready for", loggerFactory.Logger.RecordedMessages.Single()); - } +// Assert.True(pendingTask.IsCompleted); +// Assert.Contains("Timeout before work got ready for", loggerFactory.Logger.RecordedMessages.Single()); +// } - [Fact] - public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() - { - var now = DateTime.UtcNow; +// [Fact] +// public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() +// { +// var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 50); +// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 50); - var parallelQueues = - Enumerable.Range(0, 10) - .Select(_ => - Task.Run(() => { - var document = CreateTestDocument(); +// var parallelQueues = +// Enumerable.Range(0, 10) +// .Select(_ => +// Task.Run(() => { +// var document = CreateTestDocument(); - queue.PutWork(document); +// queue.PutWork(document); - PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); +// PassOverThrotlingPeriod(now); +// var work = queue.TakeWork(); - var pendingTask = queue.WaitForPendingWorkDoneEvent(work); - pendingTask.Wait(TimeSpan.FromMilliseconds(5)); +// var pendingTask = queue.WaitForPendingWorkDoneEvent(work); +// pendingTask.Wait(TimeSpan.FromMilliseconds(5)); - Assert.True(pendingTask.IsCompleted); - })) - .ToArray(); +// Assert.True(pendingTask.IsCompleted); +// })) +// .ToArray(); - await Task.WhenAll(parallelQueues); - } +// await Task.WhenAll(parallelQueues); +// } - [Fact] - public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() - { - var now = DateTime.UtcNow; - var loggerFactory = new LoggerFactory(); - var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); - var document = CreateTestDocument(); +// [Fact] +// public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() +// { +// var now = DateTime.UtcNow; +// var loggerFactory = new LoggerFactory(); +// var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); +// var document = CreateTestDocument(); - queue.PutWork(document); +// queue.PutWork(document); - now = PassOverThrotlingPeriod(now); +// now = PassOverThrotlingPeriod(now); - var work = queue.TakeWork(); - var waitingCall = Task.Run(async () => await queue.WaitForPendingWorkDoneEvent(work)); - await Task.Delay(50); +// var work = queue.TakeWork(); +// var waitingCall = Task.Run(async () => await queue.WaitForPendingWorkDoneEvent(work)); +// await Task.Delay(50); - // User updates code -> project is queued again during period when theres already api call waiting - // to continue. - queue.PutWork(document); +// // User updates code -> project is queued again during period when theres already api call waiting +// // to continue. +// queue.PutWork(document); - // First iteration of work is done. - queue.MarkWorkAsCompleteForDocument(document); +// // First iteration of work is done. +// queue.MarkWorkAsCompleteForDocument(document); - // Waiting call continues because it's iteration of work is done, even when theres next - // already waiting. - await waitingCall; +// // Waiting call continues because it's iteration of work is done, even when theres next +// // already waiting. +// await waitingCall; - Assert.True(waitingCall.IsCompleted); - Assert.Empty(loggerFactory.Logger.RecordedMessages); - } +// Assert.True(waitingCall.IsCompleted); +// Assert.Empty(loggerFactory.Logger.RecordedMessages); +// } - private Document CreateTestDocument() - { - var tempWorkspace = new AdhocWorkspace(); - return tempWorkspace.AddDocument(DocumentInfo.Create( - id: DocumentId.CreateNewId(ProjectId.CreateNewId()), - name: "testFile.cs", - sourceCodeKind: SourceCodeKind.Regular, - loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create())), - filePath: @"c:\testFile.cs")); +// private Document CreateTestDocument() +// { +// var tempWorkspace = new AdhocWorkspace(); +// return tempWorkspace.AddDocument(DocumentInfo.Create( +// id: DocumentId.CreateNewId(ProjectId.CreateNewId()), +// name: "testFile.cs", +// sourceCodeKind: SourceCodeKind.Regular, +// loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create())), +// filePath: @"c:\testFile.cs")); - } - } -} +// } +// } +// } From 3c8a471299cc4411c5bd775b80a6aad2d7a2fe24 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 16:23:43 +0200 Subject: [PATCH 141/178] Testfixes. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 35 +- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 27 +- .../AnalyzerWorkerQueueFacts.cs | 418 +++++++++--------- 3 files changed, 244 insertions(+), 236 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index f8f64618cc..01bbf3047a 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -75,28 +75,31 @@ public void MarkWorkAsCompleteForDocument(Document document) // Omnisharp V2 api expects that it can request current information of diagnostics any time, // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. - public void WaitForPendingWorkDoneEvent(ImmutableArray documents) + public Task WaitForPendingWorkDoneEvent(ImmutableArray documents) { - var currentWorkMatches = _currentWork.Where(x => documents.Any(doc => doc.Id == x.Key)); - - var pendingWorkThatDoesntExistInCurrentWork = _workQueue - .Where(x => documents.Any(doc => doc.Id == x.Key)) - .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); + return Task.Run(() => + { + var currentWorkMatches = _currentWork.Where(x => documents.Any(doc => doc.Id == x.Key)); - // Not perfect but WaitAll only accepts up to 64 handles at once. - var workToWait = currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork).Take(60); + var pendingWorkThatDoesntExistInCurrentWork = _workQueue + .Where(x => documents.Any(doc => doc.Id == x.Key)) + .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); - if(workToWait.Any()) - { - var waitComplete = WaitHandle.WaitAll( - workToWait.Select(x => x.Value.manualResetEvent).ToArray(), - _timeoutForPendingWorkMs); + // Not perfect but WaitAll only accepts up to 64 handles at once. + var workToWait = currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork).Take(60); - if(!waitComplete) + if (workToWait.Any()) { - _logger.LogError($"Timeout before work got ready. Documents waited {String.Join(",", workToWait.Select(x => x.Value.document.Name))}."); + var waitComplete = WaitHandle.WaitAll( + workToWait.Select(x => x.Value.manualResetEvent).ToArray(), + _timeoutForPendingWorkMs); + + if (!waitComplete) + { + _logger.LogError($"Timeout before work got ready. Documents waited {String.Join(",", workToWait.Select(x => x.Value.document.Name))}."); + } } - } + }); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 49bc515f59..7b7fb16d11 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -73,17 +73,14 @@ public void QueueForDiagnosis(ImmutableArray documents) QueueForAnalysis(documents); } - public Task> GetDiagnostics(ImmutableArray documents) + public async Task> GetDiagnostics(ImmutableArray documents) { - return Task.Run(() => - { - _workQueue.WaitForPendingWorkDoneEvent(documents); + await _workQueue.WaitForPendingWorkDoneEvent(documents); - return _results - .Where(x => documents.Any(doc => doc.Id == x.Key)) - .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.projectName, v))) - .ToImmutableArray(); - }); + return _results + .Where(x => documents.Any(doc => doc.Id == x.Key)) + .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.projectName, v))) + .ToImmutableArray(); } private async Task Worker() @@ -160,15 +157,15 @@ private async Task AnalyzeDocument(Project project, ImmutableArray(TState state) -// { -// return null; -// } - -// public bool IsEnabled(LogLevel logLevel) => true; - -// public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) -// { -// RecordedMessages = RecordedMessages.Add(state.ToString()); -// } - -// public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); -// } - -// private class LoggerFactory : ILoggerFactory -// { -// public Logger Logger { get; } = new Logger(); - -// public void AddProvider(ILoggerProvider provider) -// { -// } - -// public ILogger CreateLogger(string categoryName) -// { -// return Logger; -// } - -// public void Dispose() -// { -// } -// } - -// [Fact] -// public void WhenProjectsAreAddedButThrotlingIsntOverNoProjectsShouldBeReturned() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); -// var document = CreateTestDocument(); - -// queue.PutWork(document); -// Assert.Empty(queue.TakeWork()); -// } - -// [Fact] -// public void WhenProjectsAreAddedToQueueThenTheyWillBeReturned() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); -// var document = CreateTestDocument(); - -// queue.PutWork(document); - -// now = PassOverThrotlingPeriod(now); - -// Assert.Contains(document, queue.TakeWork()); -// Assert.Empty(queue.TakeWork()); -// } - -// [Fact] -// public void WhenSameProjectIsAddedMultipleTimesInRowThenThrottleProjectsAsOne() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); -// var document = CreateTestDocument(); - -// queue.PutWork(document); -// queue.PutWork(document); -// queue.PutWork(document); - -// Assert.Empty(queue.TakeWork()); - -// now = PassOverThrotlingPeriod(now); - -// Assert.Contains(document, queue.TakeWork()); -// Assert.Empty(queue.TakeWork()); -// } - -// private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(1); - -// [Fact] -// public void WhenWorkIsAddedThenWaitNextIterationOfItReady() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); -// var document = CreateTestDocument(); - -// queue.PutWork(document); +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; +using Xunit; + +namespace OmniSharp.Roslyn.CSharp.Tests +{ + public class AnalyzerWorkerQueueFacts + { + private class Logger : ILogger + { + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + RecordedMessages = RecordedMessages.Add(state.ToString()); + } + + public ImmutableArray RecordedMessages { get; set; } = ImmutableArray.Create(); + } + + private class LoggerFactory : ILoggerFactory + { + public Logger Logger { get; } = new Logger(); + + public void AddProvider(ILoggerProvider provider) + { + } + + public ILogger CreateLogger(string categoryName) + { + return Logger; + } + + public void Dispose() + { + } + } + + [Fact] + public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var document = CreateTestDocument(); + + queue.PutWork(document); + Assert.Empty(queue.TakeWork()); + } + + [Fact] + public void WhenWorksIsAddedToQueueThenTheyWillBeReturned() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var document = CreateTestDocument(); + + queue.PutWork(document); + + now = PassOverThrotlingPeriod(now); + + Assert.Contains(document, queue.TakeWork()); + Assert.Empty(queue.TakeWork()); + } + + [Fact] + public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); + var document = CreateTestDocument(); + + queue.PutWork(document); + queue.PutWork(document); + queue.PutWork(document); + + Assert.Empty(queue.TakeWork()); + + now = PassOverThrotlingPeriod(now); + + Assert.Contains(document, queue.TakeWork()); + Assert.Empty(queue.TakeWork()); + } + + private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(1); + + [Fact] + public void WhenWorkIsAddedThenWaitNextIterationOfItReady() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); + var document = CreateTestDocument(); + + queue.PutWork(document); -// var pendingTask = queue.WaitForPendingWorkDoneEvent(new [] { document }.ToImmutableArray()); -// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + var pendingTask = queue.WaitForPendingWorkDoneEvent(new [] { document }.ToImmutableArray()); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); -// Assert.False(pendingTask.IsCompleted); + Assert.False(pendingTask.IsCompleted); -// now = PassOverThrotlingPeriod(now); + now = PassOverThrotlingPeriod(now); -// var work = queue.TakeWork(); -// queue.MarkWorkAsCompleteForDocument(document); -// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); -// Assert.True(pendingTask.IsCompleted); -// } + var work = queue.TakeWork(); + queue.MarkWorkAsCompleteForDocument(document); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + Assert.True(pendingTask.IsCompleted); + } -// [Fact] -// public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() -// { -// var now = DateTime.UtcNow; -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); -// var document = CreateTestDocument(); + [Fact] + public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsReady() + { + var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); + var document = CreateTestDocument(); -// queue.PutWork(document); + queue.PutWork(document); -// now = PassOverThrotlingPeriod(now); + now = PassOverThrotlingPeriod(now); -// var work = queue.TakeWork(); + var work = queue.TakeWork(); -// var pendingTask = queue.WaitForPendingWorkDoneEvent(work); -// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + var pendingTask = queue.WaitForPendingWorkDoneEvent(work); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); -// Assert.False(pendingTask.IsCompleted); -// queue.MarkWorkAsCompleteForDocument(document); -// pendingTask.Wait(TimeSpan.FromMilliseconds(50)); -// Assert.True(pendingTask.IsCompleted); -// } + Assert.False(pendingTask.IsCompleted); + queue.MarkWorkAsCompleteForDocument(document); + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + Assert.True(pendingTask.IsCompleted); + } + + [Fact] + public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() + { + var now = DateTime.UtcNow; + var loggerFactory = new LoggerFactory(); + var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); + var document = CreateTestDocument(); -// [Fact] -// public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() -// { -// var now = DateTime.UtcNow; -// var loggerFactory = new LoggerFactory(); -// var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); -// var document = CreateTestDocument(); + queue.PutWork(document); -// queue.PutWork(document); + now = PassOverThrotlingPeriod(now); + var work = queue.TakeWork(); -// now = PassOverThrotlingPeriod(now); -// var work = queue.TakeWork(); + var pendingTask = queue.WaitForPendingWorkDoneEvent(work); + pendingTask.Wait(TimeSpan.FromMilliseconds(100)); -// var pendingTask = queue.WaitForPendingWorkDoneEvent(work); -// pendingTask.Wait(TimeSpan.FromMilliseconds(100)); + Assert.True(pendingTask.IsCompleted); + Assert.Contains("Timeout before work got ready for", loggerFactory.Logger.RecordedMessages.Single()); + } -// Assert.True(pendingTask.IsCompleted); -// Assert.Contains("Timeout before work got ready for", loggerFactory.Logger.RecordedMessages.Single()); -// } + [Fact] + public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() + { + var now = DateTime.UtcNow; -// [Fact] -// public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExpected() -// { -// var now = DateTime.UtcNow; + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 50); -// var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 50); + var parallelQueues = + Enumerable.Range(0, 10) + .Select(_ => + Task.Run(() => { + var document = CreateTestDocument(); -// var parallelQueues = -// Enumerable.Range(0, 10) -// .Select(_ => -// Task.Run(() => { -// var document = CreateTestDocument(); + queue.PutWork(document); -// queue.PutWork(document); + PassOverThrotlingPeriod(now); + var work = queue.TakeWork(); -// PassOverThrotlingPeriod(now); -// var work = queue.TakeWork(); + var pendingTask = queue.WaitForPendingWorkDoneEvent(work); + pendingTask.Wait(TimeSpan.FromMilliseconds(5)); -// var pendingTask = queue.WaitForPendingWorkDoneEvent(work); -// pendingTask.Wait(TimeSpan.FromMilliseconds(5)); + Assert.True(pendingTask.IsCompleted); + })) + .ToArray(); -// Assert.True(pendingTask.IsCompleted); -// })) -// .ToArray(); + await Task.WhenAll(parallelQueues); + } -// await Task.WhenAll(parallelQueues); -// } + [Fact] + public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() + { + var now = DateTime.UtcNow; + var loggerFactory = new LoggerFactory(); + var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); + var document = CreateTestDocument(); -// [Fact] -// public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnotherOneToGetReady() -// { -// var now = DateTime.UtcNow; -// var loggerFactory = new LoggerFactory(); -// var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); -// var document = CreateTestDocument(); + queue.PutWork(document); -// queue.PutWork(document); + now = PassOverThrotlingPeriod(now); -// now = PassOverThrotlingPeriod(now); + var work = queue.TakeWork(); + var waitingCall = Task.Run(async () => await queue.WaitForPendingWorkDoneEvent(work)); + await Task.Delay(50); -// var work = queue.TakeWork(); -// var waitingCall = Task.Run(async () => await queue.WaitForPendingWorkDoneEvent(work)); -// await Task.Delay(50); + // User updates code -> document is queued again during period when theres already api call waiting + // to continue. + queue.PutWork(document); -// // User updates code -> project is queued again during period when theres already api call waiting -// // to continue. -// queue.PutWork(document); - -// // First iteration of work is done. -// queue.MarkWorkAsCompleteForDocument(document); - -// // Waiting call continues because it's iteration of work is done, even when theres next -// // already waiting. -// await waitingCall; - -// Assert.True(waitingCall.IsCompleted); -// Assert.Empty(loggerFactory.Logger.RecordedMessages); -// } - -// private Document CreateTestDocument() -// { -// var tempWorkspace = new AdhocWorkspace(); -// return tempWorkspace.AddDocument(DocumentInfo.Create( -// id: DocumentId.CreateNewId(ProjectId.CreateNewId()), -// name: "testFile.cs", -// sourceCodeKind: SourceCodeKind.Regular, -// loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create())), -// filePath: @"c:\testFile.cs")); - -// } -// } -// } + // First iteration of work is done. + queue.MarkWorkAsCompleteForDocument(document); + + // Waiting call continues because it's iteration of work is done, even when theres next + // already waiting. + await waitingCall; + + Assert.True(waitingCall.IsCompleted); + Assert.Empty(loggerFactory.Logger.RecordedMessages); + } + + private Document CreateTestDocument() + { + var projectInfo = ProjectInfo.Create( + id: ProjectId.CreateNewId(), + version: VersionStamp.Create(), + name: "testProject", + assemblyName: "AssemblyName", + language: LanguageNames.CSharp); + + var tempWorkspace = new AdhocWorkspace(); + tempWorkspace.AddProject(projectInfo); + return tempWorkspace.AddDocument(DocumentInfo.Create( + id: DocumentId.CreateNewId(projectInfo.Id), + name: "testFile.cs", + sourceCodeKind: SourceCodeKind.Regular, + loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create())), + filePath: @"c:\testFile.cs")); + + } + } +} From 242a28b738c72c47ef4e21ce3b85c25d5693b42c Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 17:01:46 +0200 Subject: [PATCH 142/178] Testfixes. --- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 20 ++++++++++++++----- .../AnalyzerWorkerQueueFacts.cs | 20 +++++++++---------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 7b7fb16d11..d6e253b7e0 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -162,20 +162,30 @@ private async Task AnalyzeDocument(Project project, ImmutableArray.Empty; + // Only basic syntax check is available if file is miscellanous like orphan .cs file. // Those projects are on hard coded virtual project named 'MiscellaneousFiles.csproj'. - if (allAnalyzers.Any() && project.Name != "MiscellaneousFiles.csproj") + if(project.Name == "MiscellaneousFiles.csproj") + { + var syntaxTree = await document.GetSyntaxTreeAsync(); + diagnostics = syntaxTree.GetDiagnostics().ToImmutableArray(); + } + else if (allAnalyzers.Any()) { - var diagnosticsWithAnalyzers = await compiled + // Analyzers cannot be called with empty analyzer list. + var diagnosticsFromAnalyzers = await compiled .WithAnalyzers(allAnalyzers, workspaceAnalyzerOptions) - .GetAnalyzerSemanticDiagnosticsAsync(documentSemanticModel, filterSpan: null, perDocumentTimeout.Token); // This cannot be invoked with empty analyzers list. + .GetAnalyzerSemanticDiagnosticsAsync(documentSemanticModel, filterSpan: null, perDocumentTimeout.Token); - UpdateCurrentDiagnostics(project, document, diagnosticsWithAnalyzers.Concat(documentSemanticModel.GetDiagnostics()).ToImmutableArray()); + diagnostics = diagnosticsFromAnalyzers.Concat(documentSemanticModel.GetDiagnostics()).ToImmutableArray(); } else { - UpdateCurrentDiagnostics(project, document, documentSemanticModel.GetDiagnostics()); + diagnostics = documentSemanticModel.GetDiagnostics().ToImmutableArray(); } + + UpdateCurrentDiagnostics(project, document, diagnostics); } catch (Exception ex) { diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index d413362513..60e0ea1cd1 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -3,9 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using OmniSharp.Roslyn.CSharp.Workers.Diagnostics; using Xunit; @@ -69,8 +67,9 @@ public void WhenWorksIsAddedToQueueThenTheyWillBeReturned() queue.PutWork(document); now = PassOverThrotlingPeriod(now); + var work = queue.TakeWork(); - Assert.Contains(document, queue.TakeWork()); + Assert.Contains(document, work); Assert.Empty(queue.TakeWork()); } @@ -93,7 +92,7 @@ public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne() Assert.Empty(queue.TakeWork()); } - private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(1); + private static DateTime PassOverThrotlingPeriod(DateTime now) => now.AddSeconds(30); [Fact] public void WhenWorkIsAddedThenWaitNextIterationOfItReady() @@ -156,7 +155,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() pendingTask.Wait(TimeSpan.FromMilliseconds(100)); Assert.True(pendingTask.IsCompleted); - Assert.Contains("Timeout before work got ready for", loggerFactory.Logger.RecordedMessages.Single()); + Assert.Contains("Timeout before work got ready", loggerFactory.Logger.RecordedMessages.Single()); } [Fact] @@ -174,11 +173,13 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp queue.PutWork(document); - PassOverThrotlingPeriod(now); + now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); var pendingTask = queue.WaitForPendingWorkDoneEvent(work); - pendingTask.Wait(TimeSpan.FromMilliseconds(5)); + queue.MarkWorkAsCompleteForDocument(document); + + pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); })) @@ -231,10 +232,7 @@ private Document CreateTestDocument() tempWorkspace.AddProject(projectInfo); return tempWorkspace.AddDocument(DocumentInfo.Create( id: DocumentId.CreateNewId(projectInfo.Id), - name: "testFile.cs", - sourceCodeKind: SourceCodeKind.Regular, - loader: TextLoader.From(TextAndVersion.Create(SourceText.From(""), VersionStamp.Create())), - filePath: @"c:\testFile.cs")); + name: "testFile.cs")); } } From 2cd01839a97967ab849c6e6cd0b52cd5fa344565 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 17:33:19 +0200 Subject: [PATCH 143/178] Correct global.json. --- global.json | 1 + 1 file changed, 1 insertion(+) diff --git a/global.json b/global.json index b7b530d442..4628116978 100644 --- a/global.json +++ b/global.json @@ -1,4 +1,5 @@ { "sdk": { + "version": "2.1.301" } } From 8eeabdfaac609428af8f55503ece7c0a264c10f7 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 17:38:30 +0200 Subject: [PATCH 144/178] Reduced clutter from message. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 01bbf3047a..399946880c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -96,7 +96,7 @@ public Task WaitForPendingWorkDoneEvent(ImmutableArray documents) if (!waitComplete) { - _logger.LogError($"Timeout before work got ready. Documents waited {String.Join(",", workToWait.Select(x => x.Value.document.Name))}."); + _logger.LogError($"Timeout before work got ready. Documents waited {String.Join(",", workToWait.Select(x => x.Value.document.Name).Take(5))}..."); } } }); From 811cbd95855733ef59f563299e0a0c3b4f09c244 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 18:08:48 +0200 Subject: [PATCH 145/178] Build stability testing. From 5dac0aadc0695d5241d9721b9e995ab7790adce7 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 18:23:53 +0200 Subject: [PATCH 146/178] Testfix. --- .../AnalyzerWorkerQueueFacts.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 60e0ea1cd1..f9906977f0 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -163,7 +163,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp { var now = DateTime.UtcNow; - var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 50); + var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 1000); var parallelQueues = Enumerable.Range(0, 10) @@ -177,9 +177,13 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp var work = queue.TakeWork(); var pendingTask = queue.WaitForPendingWorkDoneEvent(work); - queue.MarkWorkAsCompleteForDocument(document); - pendingTask.Wait(TimeSpan.FromMilliseconds(50)); + foreach (var workDoc in work) + { + queue.MarkWorkAsCompleteForDocument(workDoc); + } + + pendingTask.Wait(TimeSpan.FromMilliseconds(300)); Assert.True(pendingTask.IsCompleted); })) From 9b4b1f0b47ea0f447343daa0c0618be5801e6361 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 18:42:08 +0200 Subject: [PATCH 147/178] Test tweak. --- .../OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index f9906977f0..b03eba0688 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -184,12 +184,12 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp } pendingTask.Wait(TimeSpan.FromMilliseconds(300)); - - Assert.True(pendingTask.IsCompleted); })) .ToArray(); await Task.WhenAll(parallelQueues); + + Assert.Empty(queue.TakeWork()); } [Fact] From ad385606a5e880997f50fd0f9153b71167bc7e0c Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 19:24:04 +0200 Subject: [PATCH 148/178] Reattempt with travis builds. From 50f25c5901956194966ae5beaf11b3a0a0343844 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 20:08:25 +0200 Subject: [PATCH 149/178] Threading fix. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 399946880c..808a6459cd 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -86,7 +86,9 @@ public Task WaitForPendingWorkDoneEvent(ImmutableArray documents) .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); // Not perfect but WaitAll only accepts up to 64 handles at once. - var workToWait = currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork).Take(60); + var workToWait = currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork) + .Take(60) + .ToArray(); if (workToWait.Any()) { From be71dd7ed5e5231268014dc086756fb4e97ad25a Mon Sep 17 00:00:00 2001 From: Savpek Date: Sat, 16 Feb 2019 22:35:58 +0200 Subject: [PATCH 150/178] Restored task based version to fix possible deadlock with xunit scheduler. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 54 +++++++++---------- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- .../AnalyzerWorkerQueueFacts.cs | 10 ++-- 3 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 808a6459cd..3db7531c98 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -13,11 +13,11 @@ public class AnalyzerWorkQueue { private readonly int _throttlingMs = 300; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); - private readonly ConcurrentDictionary _currentWork = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _currentWork = + new ConcurrentDictionary(); private readonly Func _utcNow; private readonly int _timeoutForPendingWorkMs; private readonly ILogger _logger; @@ -35,8 +35,8 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = n public void PutWork(Document document) { _workQueue.AddOrUpdate(document.Id, - (modified: DateTime.UtcNow, document, new ManualResetEvent(false)), - (_, oldValue) => (modified: DateTime.UtcNow, document, oldValue.manualResetEvent)); + (modified: DateTime.UtcNow, document, new CancellationTokenSource()), + (_, oldValue) => (modified: DateTime.UtcNow, document, oldValue.workDoneSource)); } public ImmutableArray TakeWork() @@ -67,41 +67,35 @@ public void MarkWorkAsCompleteForDocument(Document document) { if(_currentWork.TryGetValue(document.Id, out var work)) { + work.workDoneSource.Cancel(); _currentWork.TryRemove(document.Id, out _); - work.manualResetEvent.Set(); } } // Omnisharp V2 api expects that it can request current information of diagnostics any time, // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. - public Task WaitForPendingWorkDoneEvent(ImmutableArray documents) + public async Task WaitWorkReadyForDocuments(ImmutableArray documents) { - return Task.Run(() => - { - var currentWorkMatches = _currentWork.Where(x => documents.Any(doc => doc.Id == x.Key)); + var currentWorkMatches = _currentWork.Where(x => documents.Any(doc => doc.Id == x.Key)); - var pendingWorkThatDoesntExistInCurrentWork = _workQueue - .Where(x => documents.Any(doc => doc.Id == x.Key)) - .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); + var pendingWorkThatDoesntExistInCurrentWork = _workQueue + .Where(x => documents.Any(doc => doc.Id == x.Key)) + .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); - // Not perfect but WaitAll only accepts up to 64 handles at once. - var workToWait = currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork) - .Take(60) - .ToArray(); + await Task.WhenAll( + currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork) + .Select(x => Task.Delay(_timeoutForPendingWorkMs, x.Value.workDoneSource.Token) + .ContinueWith(task => LogTimeouts(task, x.Value.document.Name))) + .ToImmutableArray()); + } - if (workToWait.Any()) - { - var waitComplete = WaitHandle.WaitAll( - workToWait.Select(x => x.Value.manualResetEvent).ToArray(), - _timeoutForPendingWorkMs); - - if (!waitComplete) - { - _logger.LogError($"Timeout before work got ready. Documents waited {String.Join(",", workToWait.Select(x => x.Value.document.Name).Take(5))}..."); - } - } - }); + // This is basically asserting mechanism for hanging analysis if any. If this doesn't exist tracking + // down why results doesn't come up (for example in situation when theres bad analyzer that takes ages to complete). + private void LogTimeouts(Task task, string description) + { + if (!task.IsCanceled) _logger.LogWarning($"Timeout before work got ready for {description}."); } } + } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index d6e253b7e0..34d4a642c2 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -75,7 +75,7 @@ public void QueueForDiagnosis(ImmutableArray documents) public async Task> GetDiagnostics(ImmutableArray documents) { - await _workQueue.WaitForPendingWorkDoneEvent(documents); + await _workQueue.WaitWorkReadyForDocuments(documents); return _results .Where(x => documents.Any(doc => doc.Id == x.Key)) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index b03eba0688..6e83dee919 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -103,7 +103,7 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() queue.PutWork(document); - var pendingTask = queue.WaitForPendingWorkDoneEvent(new [] { document }.ToImmutableArray()); + var pendingTask = queue.WaitWorkReadyForDocuments(new [] { document }.ToImmutableArray()); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -129,7 +129,7 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR var work = queue.TakeWork(); - var pendingTask = queue.WaitForPendingWorkDoneEvent(work); + var pendingTask = queue.WaitWorkReadyForDocuments(work); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -151,7 +151,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitForPendingWorkDoneEvent(work); + var pendingTask = queue.WaitWorkReadyForDocuments(work); pendingTask.Wait(TimeSpan.FromMilliseconds(100)); Assert.True(pendingTask.IsCompleted); @@ -176,7 +176,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitForPendingWorkDoneEvent(work); + var pendingTask = queue.WaitWorkReadyForDocuments(work); foreach (var workDoc in work) { @@ -205,7 +205,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var waitingCall = Task.Run(async () => await queue.WaitForPendingWorkDoneEvent(work)); + var waitingCall = Task.Run(async () => await queue.WaitWorkReadyForDocuments(work)); await Task.Delay(50); // User updates code -> document is queued again during period when theres already api call waiting From e38e73eb46552bc305fc542f265d4895945a87bf Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 08:16:58 +0200 Subject: [PATCH 151/178] Retest builds. --- .../Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 34d4a642c2..3c05bcc845 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -176,7 +176,7 @@ private async Task AnalyzeDocument(Project project, ImmutableArray Date: Sun, 17 Feb 2019 08:42:40 +0200 Subject: [PATCH 152/178] Added skip to possibly hanging cake tests as test. --- tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs | 2 +- tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs | 2 +- tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs | 4 ++-- tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs | 8 ++++---- tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs b/tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs index 1f16bf3f07..e9d183fa22 100644 --- a/tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs +++ b/tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs @@ -24,7 +24,7 @@ public AutoCompleteFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.AutoComplete; - [Fact] + [Fact(Skip ="Testing it really is cake tests that hangs.")] public async Task ShouldGenerateFromHostObject() { const string input = @"Task$$"; diff --git a/tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs b/tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs index a170950055..a9eab07a3b 100644 --- a/tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs +++ b/tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs @@ -19,7 +19,7 @@ public BlockStructureFacts(ITestOutputHelper output) protected override string EndpointName => OmniSharpEndpoints.V2.BlockStructure; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task UsesRoslynBlockStructureService() { const string code = @"class Foo[| diff --git a/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs b/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs index aecf30e7b3..36de190d66 100644 --- a/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs @@ -20,7 +20,7 @@ public CakeProjectSystemFacts(ITestOutputHelper output) { } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldGetProjects() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) @@ -58,7 +58,7 @@ public async Task ShouldAddAndRemoveProjects() } } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task AllProjectsShouldUseLatestLanguageVersion() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) diff --git a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs index 6a8987e24e..906f73b5a3 100644 --- a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs @@ -22,7 +22,7 @@ public CodeActionsV2Facts(ITestOutputHelper output) { } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task Can_get_code_actions_from_roslyn() { const string code = "var regex = new Reg[||]ex();"; @@ -31,7 +31,7 @@ public async Task Can_get_code_actions_from_roslyn() Assert.Contains("using System.Text.RegularExpressions;", refactorings); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task Can_get_ranged_code_action() { const string code = @@ -69,7 +69,7 @@ public void Whatever() Assert.Equal(expected, refactorings); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task Can_extract_method() { const string code = @@ -99,7 +99,7 @@ public void Whatever() Assert.Equal(expected, modifiedFile.Changes.FirstOrDefault()); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task Should_Not_Find_Rename_File() { const string code = diff --git a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs index f6289e6e71..2b65ace639 100644 --- a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs @@ -24,7 +24,7 @@ public CodeCheckFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.CodeCheck; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() { const string input = @"zzz"; @@ -33,7 +33,7 @@ public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() Assert.NotEmpty(diagnostics.QuickFixes); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldNotCallCodeCheckServiceIfRequestDoesNotSpecifyFileName() { const string input = @"zzz$$"; @@ -42,7 +42,7 @@ public async Task ShouldNotCallCodeCheckServiceIfRequestDoesNotSpecifyFileName() Assert.Null(diagnostics); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldNotIncludeDiagnosticsFromLoadedFilesIfFileNameIsSpecified() { const string input = @" From 342a1406ac986fee0ad02ca58a95d185b2ef43f2 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 08:46:04 +0200 Subject: [PATCH 153/178] Skipped more cake tests as test. --- tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs | 6 +++--- tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs | 8 ++++---- tests/OmniSharp.Cake.Tests/RenameFacts.cs | 2 +- tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs b/tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs index f6b2ce3eab..7963149ff7 100644 --- a/tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs @@ -25,7 +25,7 @@ public CodeStructureFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.V2.CodeStructure; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task AllTypes() { const string source = @" @@ -49,7 +49,7 @@ struct S { } AssertElement(response.Elements[4], SymbolKinds.Struct, "S", "S"); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task AllTypesWithLoadedFile() { const string source = @" @@ -71,7 +71,7 @@ struct S { } AssertElement(response.Elements[4], SymbolKinds.Struct, "S", "S"); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task TestClassMembersNameRanges() { const string source = @" diff --git a/tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs b/tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs index 78264701d4..c8216ae693 100644 --- a/tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs +++ b/tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs @@ -21,28 +21,28 @@ public FindSymbolsFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.FindSymbols; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldFindSymbolsInCakeProjects() { var symbols = await FindSymbols("CakeProject", minFilterLength: null, maxItemsToReturn: null); Assert.NotEmpty(symbols.QuickFixes); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldNotFindSymbolsInCakeProjectsDueToEmptyFilter() { var symbols = await FindSymbols("CakeProject", minFilterLength: 1, maxItemsToReturn: 0); Assert.Empty(symbols.QuickFixes); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldFindLimitedNumberOfSymbolsInCakeProjects() { var symbols = await FindSymbols("CakeProject", minFilterLength: 0, maxItemsToReturn: 100); Assert.Equal(100, symbols.QuickFixes.Count()); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldNotFindSymbolsInCSharpProjects() { var symbols = await FindSymbols("ProjectAndSolution", minFilterLength: 0, maxItemsToReturn: 0); diff --git a/tests/OmniSharp.Cake.Tests/RenameFacts.cs b/tests/OmniSharp.Cake.Tests/RenameFacts.cs index 46914db148..acfe3b3512 100644 --- a/tests/OmniSharp.Cake.Tests/RenameFacts.cs +++ b/tests/OmniSharp.Cake.Tests/RenameFacts.cs @@ -22,7 +22,7 @@ public RenameFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.Rename; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldSupportLoadedFiles() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) diff --git a/tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs b/tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs index 463acc4b66..de2b77191f 100644 --- a/tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs +++ b/tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs @@ -40,7 +40,7 @@ public UpdateBufferFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.UpdateBuffer; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldSupportIncrementalChanges() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) From a2f8a63cef0f7268c2a16ded7ce991e94142794d Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 09:08:38 +0200 Subject: [PATCH 154/178] Restored some cake tests to find correct hanging one. --- tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs | 2 +- tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs | 2 +- tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs | 6 +++--- tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs | 4 ++-- tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs b/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs index 36de190d66..f7ee9f6746 100644 --- a/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs @@ -35,7 +35,7 @@ public async Task ShouldGetProjects() } } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldAddAndRemoveProjects() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) diff --git a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs index 906f73b5a3..8c3c5b05a8 100644 --- a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs @@ -47,7 +47,7 @@ public void Whatever() Assert.Contains("Extract Method", refactorings); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task Returns_ordered_code_actions() { const string code = diff --git a/tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs b/tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs index 7963149ff7..f6b2ce3eab 100644 --- a/tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeStructureFacts.cs @@ -25,7 +25,7 @@ public CodeStructureFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.V2.CodeStructure; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task AllTypes() { const string source = @" @@ -49,7 +49,7 @@ struct S { } AssertElement(response.Elements[4], SymbolKinds.Struct, "S", "S"); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task AllTypesWithLoadedFile() { const string source = @" @@ -71,7 +71,7 @@ struct S { } AssertElement(response.Elements[4], SymbolKinds.Struct, "S", "S"); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task TestClassMembersNameRanges() { const string source = @" diff --git a/tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs b/tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs index aa077adfe6..99ee6b053b 100644 --- a/tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs +++ b/tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs @@ -19,7 +19,7 @@ public FindUsagesFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.FindUsages; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldNotIncludeUsagesFromLoadedFilesWhenOnlyThisFileIsTrue() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) @@ -55,7 +55,7 @@ await GetUpdateBufferHandler(host).Handle(new UpdateBufferRequest } } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldIncludeUsagesFromLoadedFiles() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) diff --git a/tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs b/tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs index 8ef2bc0aeb..7e707b905c 100644 --- a/tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs +++ b/tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs @@ -19,7 +19,7 @@ public GotoDefinitionFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.GotoDefinition; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldSupportLoadedFiles() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) From 5edc6de4e0d5154f3a21c40d607a69fef8c6f135 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 09:20:19 +0200 Subject: [PATCH 155/178] Restored tests to see which one is broken one. --- tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs | 2 +- tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs | 2 +- tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs | 6 +++--- tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs | 8 ++++---- tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs | 4 ++-- tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs | 2 +- tests/OmniSharp.Cake.Tests/RenameFacts.cs | 2 +- tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs b/tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs index e9d183fa22..1f16bf3f07 100644 --- a/tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs +++ b/tests/OmniSharp.Cake.Tests/AutoCompleteFacts.cs @@ -24,7 +24,7 @@ public AutoCompleteFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.AutoComplete; - [Fact(Skip ="Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldGenerateFromHostObject() { const string input = @"Task$$"; diff --git a/tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs b/tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs index a9eab07a3b..a170950055 100644 --- a/tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs +++ b/tests/OmniSharp.Cake.Tests/BlockStructureFacts.cs @@ -19,7 +19,7 @@ public BlockStructureFacts(ITestOutputHelper output) protected override string EndpointName => OmniSharpEndpoints.V2.BlockStructure; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task UsesRoslynBlockStructureService() { const string code = @"class Foo[| diff --git a/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs b/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs index f7ee9f6746..aecf30e7b3 100644 --- a/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CakeProjectSystemFacts.cs @@ -20,7 +20,7 @@ public CakeProjectSystemFacts(ITestOutputHelper output) { } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldGetProjects() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) @@ -35,7 +35,7 @@ public async Task ShouldGetProjects() } } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldAddAndRemoveProjects() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy : false)) @@ -58,7 +58,7 @@ public async Task ShouldAddAndRemoveProjects() } } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task AllProjectsShouldUseLatestLanguageVersion() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) diff --git a/tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs b/tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs index c8216ae693..78264701d4 100644 --- a/tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs +++ b/tests/OmniSharp.Cake.Tests/FindSymbolsFacts.cs @@ -21,28 +21,28 @@ public FindSymbolsFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.FindSymbols; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldFindSymbolsInCakeProjects() { var symbols = await FindSymbols("CakeProject", minFilterLength: null, maxItemsToReturn: null); Assert.NotEmpty(symbols.QuickFixes); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldNotFindSymbolsInCakeProjectsDueToEmptyFilter() { var symbols = await FindSymbols("CakeProject", minFilterLength: 1, maxItemsToReturn: 0); Assert.Empty(symbols.QuickFixes); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldFindLimitedNumberOfSymbolsInCakeProjects() { var symbols = await FindSymbols("CakeProject", minFilterLength: 0, maxItemsToReturn: 100); Assert.Equal(100, symbols.QuickFixes.Count()); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldNotFindSymbolsInCSharpProjects() { var symbols = await FindSymbols("ProjectAndSolution", minFilterLength: 0, maxItemsToReturn: 0); diff --git a/tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs b/tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs index 99ee6b053b..aa077adfe6 100644 --- a/tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs +++ b/tests/OmniSharp.Cake.Tests/FindUsagesFacts.cs @@ -19,7 +19,7 @@ public FindUsagesFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.FindUsages; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldNotIncludeUsagesFromLoadedFilesWhenOnlyThisFileIsTrue() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) @@ -55,7 +55,7 @@ await GetUpdateBufferHandler(host).Handle(new UpdateBufferRequest } } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldIncludeUsagesFromLoadedFiles() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) diff --git a/tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs b/tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs index 7e707b905c..8ef2bc0aeb 100644 --- a/tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs +++ b/tests/OmniSharp.Cake.Tests/GotoDefinitionFacts.cs @@ -19,7 +19,7 @@ public GotoDefinitionFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.GotoDefinition; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldSupportLoadedFiles() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) diff --git a/tests/OmniSharp.Cake.Tests/RenameFacts.cs b/tests/OmniSharp.Cake.Tests/RenameFacts.cs index acfe3b3512..46914db148 100644 --- a/tests/OmniSharp.Cake.Tests/RenameFacts.cs +++ b/tests/OmniSharp.Cake.Tests/RenameFacts.cs @@ -22,7 +22,7 @@ public RenameFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.Rename; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldSupportLoadedFiles() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) diff --git a/tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs b/tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs index de2b77191f..463acc4b66 100644 --- a/tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs +++ b/tests/OmniSharp.Cake.Tests/UpdateBufferFacts.cs @@ -40,7 +40,7 @@ public UpdateBufferFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.UpdateBuffer; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldSupportIncrementalChanges() { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("CakeProject", shadowCopy: false)) From 751f3c5f3b311ab989a2d52557dcd2f74b81d404 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 09:20:49 +0200 Subject: [PATCH 156/178] Restored CodeCheckFacts --- tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs index 2b65ace639..f6289e6e71 100644 --- a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs @@ -24,7 +24,7 @@ public CodeCheckFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.CodeCheck; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() { const string input = @"zzz"; @@ -33,7 +33,7 @@ public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() Assert.NotEmpty(diagnostics.QuickFixes); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldNotCallCodeCheckServiceIfRequestDoesNotSpecifyFileName() { const string input = @"zzz$$"; @@ -42,7 +42,7 @@ public async Task ShouldNotCallCodeCheckServiceIfRequestDoesNotSpecifyFileName() Assert.Null(diagnostics); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldNotIncludeDiagnosticsFromLoadedFilesIfFileNameIsSpecified() { const string input = @" From 856e40ef053df6c47a623a060ef94c57b3b43f61 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 09:22:40 +0200 Subject: [PATCH 157/178] Skipped code checks again because built was aborted. --- tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs index f6289e6e71..2b65ace639 100644 --- a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs @@ -24,7 +24,7 @@ public CodeCheckFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.CodeCheck; - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() { const string input = @"zzz"; @@ -33,7 +33,7 @@ public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() Assert.NotEmpty(diagnostics.QuickFixes); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldNotCallCodeCheckServiceIfRequestDoesNotSpecifyFileName() { const string input = @"zzz$$"; @@ -42,7 +42,7 @@ public async Task ShouldNotCallCodeCheckServiceIfRequestDoesNotSpecifyFileName() Assert.Null(diagnostics); } - [Fact] + [Fact(Skip = "Testing it really is cake tests that hangs.")] public async Task ShouldNotIncludeDiagnosticsFromLoadedFilesIfFileNameIsSpecified() { const string input = @" From 4753e64d46aa29a8b3c409b394e9f15e142bb12a Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 11:17:40 +0200 Subject: [PATCH 158/178] CodeCheckFacts from cake tests unignored. --- tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs index 2b65ace639..f6289e6e71 100644 --- a/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeCheckFacts.cs @@ -24,7 +24,7 @@ public CodeCheckFacts(ITestOutputHelper testOutput) : base(testOutput) protected override string EndpointName => OmniSharpEndpoints.CodeCheck; - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() { const string input = @"zzz"; @@ -33,7 +33,7 @@ public async Task ShouldProvideDiagnosticsIfRequestContainsCakeFileName() Assert.NotEmpty(diagnostics.QuickFixes); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldNotCallCodeCheckServiceIfRequestDoesNotSpecifyFileName() { const string input = @"zzz$$"; @@ -42,7 +42,7 @@ public async Task ShouldNotCallCodeCheckServiceIfRequestDoesNotSpecifyFileName() Assert.Null(diagnostics); } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task ShouldNotIncludeDiagnosticsFromLoadedFilesIfFileNameIsSpecified() { const string input = @" From ce0358c63204cdfd1db91919fa4145776739c89b Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 11:42:18 +0200 Subject: [PATCH 159/178] Restored Can_get_code_actions_from_roslyn to check if it is hanging one. --- tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs index 8c3c5b05a8..12b1e73515 100644 --- a/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Cake.Tests/CodeActionsV2Facts.cs @@ -22,7 +22,7 @@ public CodeActionsV2Facts(ITestOutputHelper output) { } - [Fact(Skip = "Testing it really is cake tests that hangs.")] + [Fact] public async Task Can_get_code_actions_from_roslyn() { const string code = "var regex = new Reg[||]ex();"; From f886c8b16775ff4ab9cae609ea1d1f2b01f6310e Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 17:08:57 +0200 Subject: [PATCH 160/178] Refactored initialization routine for diagnostics and updated test defaults. --- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 30 ++++++++++++++----- .../CodeActionsV2Facts.cs | 8 ++--- .../CodeActionsV2Facts.cs | 19 ++++-------- .../CustomRoslynAnalyzerFacts.cs | 11 +++---- tests/TestUtility/OmniSharpTestHost.cs | 6 ---- tests/TestUtility/TestHelpers.cs | 14 +++++++++ 6 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 3c05bcc845..f293032422 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -34,6 +34,7 @@ public class CSharpDiagnosticWorkerWithAnalyzers : ICsDiagnosticWorker // Currently roslyn doesn't expose official way to use IDE analyzers during analysis. // This options gives certain IDE analysis access for services that are not yet publicly available. private readonly ConstructorInfo _workspaceAnalyzerOptionsConstructor; + private bool _initialSolutionAnalysisInvoked = false; public CSharpDiagnosticWorkerWithAnalyzers( OmniSharpWorkspace workspace, @@ -58,14 +59,27 @@ public CSharpDiagnosticWorkerWithAnalyzers( _workspace.WorkspaceChanged += OnWorkspaceChanged; - Task.Run(async () => + Task.Factory.StartNew(Worker, TaskCreationOptions.LongRunning); + } + + private Task InitializeWithWorkspaceDocumentsIfNotYetDone() + { + if (_initialSolutionAnalysisInvoked) + return Task.CompletedTask; + + _initialSolutionAnalysisInvoked = true; + + return Task.Run(async () => + { + while (!_workspace.Initialized || _workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(50); + }) + .ContinueWith(_ => Task.Delay(50)) + .ContinueWith(_ => { - while (!workspace.Initialized || workspace.CurrentSolution.Projects.Count() == 0) await Task.Delay(200); - QueueForAnalysis(workspace.CurrentSolution.Projects.SelectMany(x => x.Documents).ToImmutableArray()); - _logger.LogInformation("Solution initialized -> queue all projects for code analysis."); + var documents = _workspace.CurrentSolution.Projects.SelectMany(x => x.Documents).ToImmutableArray(); + QueueForAnalysis(documents); + _logger.LogInformation($"Solution initialized -> queue all documents for code analysis. Initial document count: {documents.Length}."); }); - - Task.Factory.StartNew(Worker, TaskCreationOptions.LongRunning); } public void QueueForDiagnosis(ImmutableArray documents) @@ -75,6 +89,8 @@ public void QueueForDiagnosis(ImmutableArray documents) public async Task> GetDiagnostics(ImmutableArray documents) { + await InitializeWithWorkspaceDocumentsIfNotYetDone(); + await _workQueue.WaitWorkReadyForDocuments(documents); return _results @@ -166,7 +182,7 @@ private async Task AnalyzeDocument(Project project, ImmutableArray configuration.Add(item.Key, item.Value)); - - var refactorings = await FindRefactoringsAsync(code, configuration); + var refactorings = await FindRefactoringsAsync(code, + TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled, existingConfiguration: configuration)); Assert.NotEmpty(refactorings); Assert.Contains("Add ConfigureAwait(false)", refactorings.Select(x => x.Name)); @@ -181,7 +179,7 @@ private static void NewMethod() public async Task Can_generate_type_and_return_name_of_new_file(bool roslynAnalyzersEnabled) { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectWithMissingType")) - using (var host = CreateOmniSharpHost(testProject.Directory, configurationData: GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled))) + using (var host = CreateOmniSharpHost(testProject.Directory, configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled))) { var requestHandler = host.GetRequestHandler(OmniSharpEndpoints.V2.RunCodeAction); var document = host.Workspace.CurrentSolution.Projects.First().Documents.First(); @@ -222,7 +220,7 @@ internal class Z public async Task Can_send_rename_and_fileOpen_responses_when_codeAction_renames_file(bool roslynAnalyzersEnabled) { using (var testProject = await TestAssets.Instance.GetTestProjectAsync("ProjectWithMismatchedFileName")) - using (var host = CreateOmniSharpHost(testProject.Directory, configurationData: GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled))) + using (var host = CreateOmniSharpHost(testProject.Directory, configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled))) { var requestHandler = host.GetRequestHandler(OmniSharpEndpoints.V2.RunCodeAction); var document = host.Workspace.CurrentSolution.Projects.First().Documents.First(); @@ -261,7 +259,7 @@ private static string TrimLines(string source) private async Task RunRefactoringAsync(string code, string refactoringName, bool wantsChanges = false, bool roslynAnalyzersEnabled = false) { - var refactorings = await FindRefactoringsAsync(code, configurationData: GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled)); + var refactorings = await FindRefactoringsAsync(code, configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled)); Assert.Contains(refactoringName, refactorings.Select(a => a.Name)); var identifier = refactorings.First(action => action.Name.Equals(refactoringName)).Identifier; @@ -270,16 +268,11 @@ private async Task RunRefactoringAsync(string code, strin private async Task> FindRefactoringNamesAsync(string code, bool roslynAnalyzersEnabled = false) { - var codeActions = await FindRefactoringsAsync(code, configurationData: GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled)); + var codeActions = await FindRefactoringsAsync(code, configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled)); return codeActions.Select(a => a.Name); } - private Dictionary GetConfigurationDataWithAnalyzerConfig(bool roslynAnalyzersEnabled) - { - return new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString() } }; - } - private async Task> FindRefactoringsAsync(string code, IDictionary configurationData = null) { var testFile = new TestFile(BufferPath, code); diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index e8feadec92..8f1529276c 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -1,18 +1,15 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using OmniSharp.Models.CodeCheck; using OmniSharp.Models.Diagnostics; using OmniSharp.Roslyn.CSharp.Services.Diagnostics; using TestUtility; using Xunit; using Xunit.Abstractions; -using Xunit.Sdk; namespace OmniSharp.Roslyn.CSharp.Tests { @@ -116,7 +113,8 @@ public async Task When_custom_analyzers_are_executed_then_return_results() private OmniSharpTestHost GetHost() { - return OmniSharpTestHost.Create(testOutput: _testOutput); + return OmniSharpTestHost.Create(testOutput: _testOutput, + configurationData: TestHelpers.GetConfigurationDataWithAnalyzerConfig(roslynAnalyzersEnabled: true)); } [Fact] @@ -129,6 +127,9 @@ public async Task Always_return_results_from_net_default_analyzers() AddProjectWitFile(host, testFile); var result = await host.RequestCodeCheckAsync(); + + result = await host.RequestCodeCheckAsync(); + Assert.Contains(result.QuickFixes.Where(x => x.FileName == testFile.FileName), f => f.Text.Contains("CS")); } } diff --git a/tests/TestUtility/OmniSharpTestHost.cs b/tests/TestUtility/OmniSharpTestHost.cs index 011c5c39d6..e81b14f6e6 100644 --- a/tests/TestUtility/OmniSharpTestHost.cs +++ b/tests/TestUtility/OmniSharpTestHost.cs @@ -106,12 +106,6 @@ public static OmniSharpTestHost Create( { var environment = new OmniSharpEnvironment(path, logLevel: LogLevel.Trace); - // During testing new analysis service is used as default. - if(configurationData == null) - { - configurationData = new [] { new KeyValuePair("RoslynExtensionsOptions:EnableAnalyzersSupport", "true") }; - } - var serviceProvider = TestServiceProvider.Create(testOutput, environment, configurationData, dotNetCliVersion); return Create(serviceProvider, additionalExports, callerName); diff --git a/tests/TestUtility/TestHelpers.cs b/tests/TestUtility/TestHelpers.cs index ee4cddb28a..260197aca3 100644 --- a/tests/TestUtility/TestHelpers.cs +++ b/tests/TestUtility/TestHelpers.cs @@ -118,5 +118,19 @@ public static MSBuildInstance AddDotNetCoreToFakeInstance(this MSBuildInstance i return instance; } + + public static Dictionary GetConfigurationDataWithAnalyzerConfig(bool roslynAnalyzersEnabled = false, Dictionary existingConfiguration = null) + { + if(existingConfiguration == null) + { + return new Dictionary() { { "RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString() } }; + } + + var copyOfExistingConfigs = existingConfiguration.ToDictionary(x => x.Key, x => x.Value); + copyOfExistingConfigs.Add("RoslynExtensionsOptions:EnableAnalyzersSupport", roslynAnalyzersEnabled.ToString()); + + return copyOfExistingConfigs; + + } } } From dd98cfed39755ca7bac74228174f8d7cd1c84771 Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 17:40:04 +0200 Subject: [PATCH 161/178] Bugfix. --- .../Diagnostics/CSharpDiagnosticWorker.cs | 20 ++++- .../BufferManagerMiscFilesFacts.cs | 86 +++++++++---------- 2 files changed, 59 insertions(+), 47 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 7e10bb7e86..eb6d112706 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -151,14 +151,28 @@ public void QueueForDiagnosis(ImmutableArray documents) foreach(var document in documents) { - var semanticModel = await document.GetSemanticModelAsync(); - var diagnostics = semanticModel.GetDiagnostics(); var projectName = document.Project.Name; - + var diagnostics = await GetDiagnosticsForDocument(document, projectName); results.AddRange(diagnostics.Select(x => (projectName: document.Project.Name, diagnostic: x))); } return results.ToImmutableArray(); } + + private static async Task> GetDiagnosticsForDocument(Document document, string projectName) + { + // Only basic syntax check is available if file is miscellanous like orphan .cs file. + // Those projects are on hard coded virtual project named 'MiscellaneousFiles.csproj'. + if (projectName == "MiscellaneousFiles.csproj") + { + var syntaxTree = await document.GetSyntaxTreeAsync(); + return syntaxTree.GetDiagnostics().ToImmutableArray(); + } + else + { + var semanticModel = await document.GetSemanticModelAsync(); + return semanticModel.GetDiagnostics(); + } + } } } \ No newline at end of file diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/BufferManagerMiscFilesFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/BufferManagerMiscFilesFacts.cs index b50a0e7b3d..a82f487b2c 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/BufferManagerMiscFilesFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/BufferManagerMiscFilesFacts.cs @@ -52,27 +52,26 @@ public static void Main(){ } }"; var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + using (var host = CreateOmniSharpHost(testProject.Directory)) { - using (var host = CreateOmniSharpHost(testProject.Directory)) + var filePath = await AddTestFile(host, testProject, testfile); + var point = testfile.Content.GetPointFromPosition(); + var request = new SignatureHelpRequest() { - var filePath = await AddTestFile(host, testProject, testfile); - var point = testfile.Content.GetPointFromPosition(); - var request = new SignatureHelpRequest() - { - FileName = filePath, - Line = point.Line, - Column = point.Offset, - Buffer = testfile.Content.Code - }; + FileName = filePath, + Line = point.Line, + Column = point.Offset, + Buffer = testfile.Content.Code + }; - var actual = await host.GetResponse(OmniSharpEndpoints.SignatureHelp, request); - Assert.Single(actual.Signatures); - Assert.Equal(0, actual.ActiveParameter); - Assert.Equal(0, actual.ActiveSignature); - Assert.Equal("NewGuid", actual.Signatures.ElementAt(0).Name); - Assert.Empty(actual.Signatures.ElementAt(0).Parameters); - } + var actual = await host.GetResponse(OmniSharpEndpoints.SignatureHelp, request); + Assert.Single(actual.Signatures); + Assert.Equal(0, actual.ActiveParameter); + Assert.Equal(0, actual.ActiveSignature); + Assert.Equal("NewGuid", actual.Signatures.ElementAt(0).Name); + Assert.Empty(actual.Signatures.ElementAt(0).Parameters); } } @@ -87,24 +86,23 @@ public void Foo() {} }"; var testfile = new TestFile("a.cs", source); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + using (var host = CreateOmniSharpHost(testProject.Directory)) { - using (var host = CreateOmniSharpHost(testProject.Directory)) + var filePath = await AddTestFile(host, testProject, testfile); + var point = testfile.Content.GetPointFromPosition(); + var request = new FindImplementationsRequest() { - var filePath = await AddTestFile(host, testProject, testfile); - var point = testfile.Content.GetPointFromPosition(); - var request = new FindImplementationsRequest() - { - FileName = filePath, - Line = point.Line, - Column = point.Offset, - Buffer = testfile.Content.Code - }; + FileName = filePath, + Line = point.Line, + Column = point.Offset, + Buffer = testfile.Content.Code + }; - var actual = await host.GetResponse(OmniSharpEndpoints.FindImplementations, request); - Assert.Single(actual.QuickFixes); - Assert.Equal("public void Foo() {}", actual.QuickFixes.First().Text.Trim()); - } + var actual = await host.GetResponse(OmniSharpEndpoints.FindImplementations, request); + Assert.Single(actual.QuickFixes); + Assert.Equal("public void Foo() {}", actual.QuickFixes.First().Text.Trim()); } } @@ -250,24 +248,24 @@ public method1() public async Task Adds_Misc_Document_Which_Supports_TypeLookup() { const string code = @"class F$$oo {}"; + var testfile = new TestFile("a.cs", code); + using (var testProject = await TestAssets.Instance.GetTestProjectAsync("EmptyProject")) + using (var host = CreateOmniSharpHost(testProject.Directory)) { - using (var host = CreateOmniSharpHost(testProject.Directory)) + var filePath = await AddTestFile(host, testProject, testfile); + var service = host.GetRequestHandler(OmniSharpEndpoints.TypeLookup); + var point = testfile.Content.GetPointFromPosition(); + var request = new TypeLookupRequest { - var filePath = await AddTestFile(host, testProject, testfile); - var service = host.GetRequestHandler(OmniSharpEndpoints.TypeLookup); - var point = testfile.Content.GetPointFromPosition(); - var request = new TypeLookupRequest - { - FileName = filePath, - Line = point.Line, - Column = point.Offset, - }; + FileName = filePath, + Line = point.Line, + Column = point.Offset, + }; - var actual = await host.GetResponse(OmniSharpEndpoints.TypeLookup, request); - Assert.Equal("Foo", actual.Type); - } + var actual = await host.GetResponse(OmniSharpEndpoints.TypeLookup, request); + Assert.Equal("Foo", actual.Type); } } From 05c8ed54777836e3144bd6a0943c1fe650e5acef Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 17 Feb 2019 18:06:09 +0200 Subject: [PATCH 162/178] Test build stability. From da6089d2194d189fce32301b8dec4a7c85822853 Mon Sep 17 00:00:00 2001 From: Savpek Date: Mon, 18 Feb 2019 19:17:16 +0200 Subject: [PATCH 163/178] Tiny tweak for logging and comment. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 3db7531c98..2bc320feac 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -90,11 +90,11 @@ await Task.WhenAll( .ToImmutableArray()); } - // This is basically asserting mechanism for hanging analysis if any. If this doesn't exist tracking - // down why results doesn't come up (for example in situation when theres bad analyzer that takes ages to complete). + // This logs wait's for document diagnostics that continue without getting current version from analyzer. + // This happens on larger solutions during initial load or situations where analysis slows down remarkably. private void LogTimeouts(Task task, string description) { - if (!task.IsCanceled) _logger.LogWarning($"Timeout before work got ready for {description}."); + if (!task.IsCanceled) _logger.LogDebug($"Timeout before work got ready for {description}."); } } From 8afa925ddedb21e9b76810a6f739014ea03f7e48 Mon Sep 17 00:00:00 2001 From: Ravi Chande Date: Thu, 21 Feb 2019 16:28:18 -0800 Subject: [PATCH 164/178] Simplify method --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 2bc320feac..05960bb82c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -77,17 +78,25 @@ public void MarkWorkAsCompleteForDocument(Document document) // like it's syncronous even that actual analysis may take a while. public async Task WaitWorkReadyForDocuments(ImmutableArray documents) { - var currentWorkMatches = _currentWork.Where(x => documents.Any(doc => doc.Id == x.Key)); + List<(DateTime modified, Document document, CancellationTokenSource workDoneSource)> items = + new List<(DateTime modified, Document document, CancellationTokenSource workDoneSource)>(); - var pendingWorkThatDoesntExistInCurrentWork = _workQueue - .Where(x => documents.Any(doc => doc.Id == x.Key)) - .Where(x => !currentWorkMatches.Any(currentWork => currentWork.Key == x.Key)); + foreach (var document in documents) + { + var id = document.Id; + if (_currentWork.ContainsKey(id)) + { + items.Add(_currentWork[id]); + } + else if (_workQueue.ContainsKey(id)) + { + items.Add(_workQueue[id]); + } + } - await Task.WhenAll( - currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork) - .Select(x => Task.Delay(_timeoutForPendingWorkMs, x.Value.workDoneSource.Token) - .ContinueWith(task => LogTimeouts(task, x.Value.document.Name))) - .ToImmutableArray()); + await Task.WhenAll(items.Select(item => + Task.Delay(_timeoutForPendingWorkMs, item.workDoneSource.Token) + .ContinueWith(task => LogTimeouts(task, item.document.Name)))); } // This logs wait's for document diagnostics that continue without getting current version from analyzer. From 76cba8713318575dee6f9be5dc9de6b82eaafefb Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 22 Feb 2019 16:07:11 +0200 Subject: [PATCH 165/178] Review fixes. --- .../V2/CachingCodeFixProviderForProjects.cs | 3 ++- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 12 +++++------- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- .../AnalyzerWorkerQueueFacts.cs | 10 +++++----- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs index 48d7c5e95f..9f7561a062 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/CachingCodeFixProviderForProjects.cs @@ -31,6 +31,7 @@ public IEnumerable GetAllCodeFixesForProject(ProjectId projectI { if (_cache.ContainsKey(projectId)) return _cache[projectId]; + return Enumerable.Empty(); } @@ -48,7 +49,7 @@ public void LoadFrom(ProjectInfo project) if (attribute?.Languages != null && attribute.Languages.Contains(project.Language)) { - return (CodeFixProvider)Activator.CreateInstance(x.AsType()); + return x.AsType().CreateInstance(); } _logger.LogInformation($"Skipping code fix provider '{x.AsType()}' because it's language doesn't match '{project.Language}'."); diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 2bc320feac..304ea2f214 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -19,17 +19,15 @@ public class AnalyzerWorkQueue private readonly ConcurrentDictionary _currentWork = new ConcurrentDictionary(); private readonly Func _utcNow; - private readonly int _timeoutForPendingWorkMs; + private readonly int _maximumDelayWhenWaitingForResults; private readonly ILogger _logger; public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = null, int timeoutForPendingWorkMs = 15*1000) { - if(utcNow == null) - utcNow = () => DateTime.UtcNow; - + utcNow = utcNow ?? (() => DateTime.UtcNow); _logger = loggerFactory.CreateLogger(); _utcNow = utcNow; - _timeoutForPendingWorkMs = timeoutForPendingWorkMs; + _maximumDelayWhenWaitingForResults = timeoutForPendingWorkMs; } public void PutWork(Document document) @@ -75,7 +73,7 @@ public void MarkWorkAsCompleteForDocument(Document document) // Omnisharp V2 api expects that it can request current information of diagnostics any time, // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. - public async Task WaitWorkReadyForDocuments(ImmutableArray documents) + public async Task WaitForResultsAsync(ImmutableArray documents) { var currentWorkMatches = _currentWork.Where(x => documents.Any(doc => doc.Id == x.Key)); @@ -85,7 +83,7 @@ public async Task WaitWorkReadyForDocuments(ImmutableArray documents) await Task.WhenAll( currentWorkMatches.Concat(pendingWorkThatDoesntExistInCurrentWork) - .Select(x => Task.Delay(_timeoutForPendingWorkMs, x.Value.workDoneSource.Token) + .Select(x => Task.Delay(_maximumDelayWhenWaitingForResults, x.Value.workDoneSource.Token) .ContinueWith(task => LogTimeouts(task, x.Value.document.Name))) .ToImmutableArray()); } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index f293032422..f014c9b279 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -91,7 +91,7 @@ public void QueueForDiagnosis(ImmutableArray documents) { await InitializeWithWorkspaceDocumentsIfNotYetDone(); - await _workQueue.WaitWorkReadyForDocuments(documents); + await _workQueue.WaitForResultsAsync(documents); return _results .Where(x => documents.Any(doc => doc.Id == x.Key)) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 6e83dee919..8eab74258b 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -103,7 +103,7 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() queue.PutWork(document); - var pendingTask = queue.WaitWorkReadyForDocuments(new [] { document }.ToImmutableArray()); + var pendingTask = queue.WaitForResultsAsync(new [] { document }.ToImmutableArray()); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -129,7 +129,7 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR var work = queue.TakeWork(); - var pendingTask = queue.WaitWorkReadyForDocuments(work); + var pendingTask = queue.WaitForResultsAsync(work); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); @@ -151,7 +151,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitWorkReadyForDocuments(work); + var pendingTask = queue.WaitForResultsAsync(work); pendingTask.Wait(TimeSpan.FromMilliseconds(100)); Assert.True(pendingTask.IsCompleted); @@ -176,7 +176,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var pendingTask = queue.WaitWorkReadyForDocuments(work); + var pendingTask = queue.WaitForResultsAsync(work); foreach (var workDoc in work) { @@ -205,7 +205,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - var waitingCall = Task.Run(async () => await queue.WaitWorkReadyForDocuments(work)); + var waitingCall = Task.Run(async () => await queue.WaitForResultsAsync(work)); await Task.Delay(50); // User updates code -> document is queued again during period when theres already api call waiting From 4e4d01c376738e4a936fed99e62c34eb058d56ea Mon Sep 17 00:00:00 2001 From: Savpek Date: Sun, 24 Feb 2019 15:13:16 +0200 Subject: [PATCH 166/178] Small tweak, startup with larger projects. --- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 8a51d6a6b7..8fc183a456 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -45,6 +45,8 @@ public ImmutableArray TakeWork() var now = _utcNow(); var currentWork = _workQueue .Where(x => ThrottlingPeriodNotActive(x.Value.modified, now)) + .OrderByDescending(x => x.Value.modified) + .Take(50) .ToImmutableArray(); foreach (var work in currentWork) From 01cafcb3f621f7b1b4c82a49d98876976cbcf23f Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 26 Feb 2019 20:55:36 +0200 Subject: [PATCH 167/178] Initial fixes for syntax analysis rulesets. --- .../Services/Diagnostics/CodeCheckService.cs | 24 +++-- .../Diagnostics/DiagnosticsService.cs | 10 +-- .../Refactoring/V2/BaseCodeActionService.cs | 2 +- .../Workers/Diagnostics/AnalyzerWorkQueue.cs | 50 +++++------ .../Diagnostics/CSharpDiagnosticWorker.cs | 24 +++-- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 87 +++++++++++++++---- .../CsharpDiagnosticWorkerComposer.cs | 21 ++++- .../Diagnostics/ICsDiagnosticWorker.cs | 6 +- .../AnalyzerWorkerQueueFacts.cs | 43 ++++----- .../DiagnosticsV2Facts.cs | 6 +- 10 files changed, 173 insertions(+), 100 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 63358a2858..2166d8cfeb 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -38,20 +38,26 @@ public CodeCheckService( public async Task Handle(CodeCheckRequest request) { - var documents = request.FileName != null - // To properly handle the request wait until all projects are loaded. - ? await _workspace.GetDocumentsFromFullProjectModelAsync(request.FileName) - : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); + if (request.FileName == null) + { + var allDiagnostics = await _diagWorker.GetAllDiagnostics(); + return GetResponseFromDiagnostics(allDiagnostics, fileName: null); + } - var diagnostics = await _diagWorker.GetDiagnostics(documents.ToImmutableArray()); + var diagnostics = await _diagWorker.GetDiagnostics(new [] { request.FileName }.ToImmutableArray()); - var locations = diagnostics - .Where(x => (string.IsNullOrEmpty(request.FileName) - || x.diagnostic.Location.GetLineSpan().Path == request.FileName)) + return GetResponseFromDiagnostics(diagnostics, request.FileName); + } + + private static QuickFixResponse GetResponseFromDiagnostics(ImmutableArray<(string projectName, Diagnostic diagnostic)> diagnostics, string fileName) + { + var diagnosticLocations = diagnostics + .Where(x => (string.IsNullOrEmpty(fileName) + || x.diagnostic.Location.GetLineSpan().Path == fileName)) .DistinctDiagnosticLocationsByProject() .Where(x => x.FileName != null); - return new QuickFixResponse(locations); + return new QuickFixResponse(diagnosticLocations); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs index b07f2455d6..65b0f9666c 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/DiagnosticsService.cs @@ -13,14 +13,12 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics public class DiagnosticsService : IRequestHandler { private readonly DiagnosticEventForwarder _forwarder; - private readonly OmniSharpWorkspace _workspace; private readonly ICsDiagnosticWorker _diagWorker; [ImportingConstructor] - public DiagnosticsService(OmniSharpWorkspace workspace, DiagnosticEventForwarder forwarder, ICsDiagnosticWorker diagWorker) + public DiagnosticsService(DiagnosticEventForwarder forwarder, ICsDiagnosticWorker diagWorker) { _forwarder = forwarder; - _workspace = workspace; _diagWorker = diagWorker; } @@ -31,11 +29,7 @@ public Task Handle(DiagnosticsRequest request) _forwarder.IsEnabled = true; } - var documents = request.FileName != null - ? _workspace.GetDocuments(request.FileName) - : _workspace.CurrentSolution.Projects.SelectMany(project => project.Documents); - - _diagWorker.QueueForDiagnosis(documents.ToImmutableArray()); + _diagWorker.QueueAllDocumentsForDiagnostics(); return Task.FromResult(new DiagnosticsResponse()); } diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs index 5bba7b4923..4aadcadfd0 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Refactoring/V2/BaseCodeActionService.cs @@ -112,7 +112,7 @@ private TextSpan GetTextSpan(ICodeActionRequest request, SourceText sourceText) private async Task CollectCodeFixesActions(Document document, TextSpan span, List codeActions) { - var diagnosticsWithProjects = await this.diagnostics.GetDiagnostics(ImmutableArray.Create(document)); + var diagnosticsWithProjects = await this.diagnostics.GetDiagnostics(ImmutableArray.Create(document.FilePath)); var groupedBySpan = diagnosticsWithProjects .Select(x => x.diagnostic) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs index 8fc183a456..a27af877fe 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/AnalyzerWorkQueue.cs @@ -14,11 +14,12 @@ public class AnalyzerWorkQueue { private readonly int _throttlingMs = 300; - private readonly ConcurrentDictionary _workQueue = - new ConcurrentDictionary(); + private readonly ConcurrentDictionary _workQueue = + new ConcurrentDictionary(); + + private readonly ConcurrentDictionary _currentWork = + new ConcurrentDictionary(); - private readonly ConcurrentDictionary _currentWork = - new ConcurrentDictionary(); private readonly Func _utcNow; private readonly int _maximumDelayWhenWaitingForResults; private readonly ILogger _logger; @@ -31,14 +32,14 @@ public AnalyzerWorkQueue(ILoggerFactory loggerFactory, Func utcNow = n _maximumDelayWhenWaitingForResults = timeoutForPendingWorkMs; } - public void PutWork(Document document) + public void PutWork(DocumentId documentId) { - _workQueue.AddOrUpdate(document.Id, - (modified: DateTime.UtcNow, document, new CancellationTokenSource()), - (_, oldValue) => (modified: DateTime.UtcNow, document, oldValue.workDoneSource)); + _workQueue.AddOrUpdate(documentId, + (modified: DateTime.UtcNow, new CancellationTokenSource()), + (_, oldValue) => (modified: DateTime.UtcNow, oldValue.workDoneSource)); } - public ImmutableArray TakeWork() + public ImmutableArray TakeWork() { lock (_workQueue) { @@ -55,7 +56,7 @@ public ImmutableArray TakeWork() _currentWork.TryAdd(work.Key, work.Value); } - return currentWork.Select(x => x.Value.document).ToImmutableArray(); + return currentWork.Select(x => x.Key).ToImmutableArray(); } } @@ -64,45 +65,44 @@ private bool ThrottlingPeriodNotActive(DateTime modified, DateTime now) return (now - modified).TotalMilliseconds >= _throttlingMs; } - public void MarkWorkAsCompleteForDocument(Document document) + public void MarkWorkAsCompleteForDocumentId(DocumentId documentId) { - if(_currentWork.TryGetValue(document.Id, out var work)) + if(_currentWork.TryGetValue(documentId, out var work)) { work.workDoneSource.Cancel(); - _currentWork.TryRemove(document.Id, out _); + _currentWork.TryRemove(documentId, out _); } } // Omnisharp V2 api expects that it can request current information of diagnostics any time, // however analysis is worker based and is eventually ready. This method is used to make api look // like it's syncronous even that actual analysis may take a while. - public async Task WaitForResultsAsync(ImmutableArray documents) + public async Task WaitForResultsAsync(ImmutableArray documentIds) { - var items = new List<(DateTime modified, Document document, CancellationTokenSource workDoneSource)>(); + var items = new List<(DateTime modified, CancellationTokenSource workDoneSource)>(); - foreach (var document in documents) + foreach (var documentId in documentIds) { - var id = document.Id; - if (_currentWork.ContainsKey(id)) + if (_currentWork.ContainsKey(documentId)) { - items.Add(_currentWork[id]); + items.Add(_currentWork[documentId]); } - else if (_workQueue.ContainsKey(id)) + else if (_workQueue.ContainsKey(documentId)) { - items.Add(_workQueue[id]); + items.Add(_workQueue[documentId]); } } await Task.WhenAll(items.Select(item => Task.Delay(_maximumDelayWhenWaitingForResults, item.workDoneSource.Token) - .ContinueWith(task => LogTimeouts(task, item.document.Name)))); + .ContinueWith(task => LogTimeouts(task, documentIds)))); } - // This logs wait's for document diagnostics that continue without getting current version from analyzer. + // This logs wait's for documentId diagnostics that continue without getting current version from analyzer. // This happens on larger solutions during initial load or situations where analysis slows down remarkably. - private void LogTimeouts(Task task, string description) + private void LogTimeouts(Task task, IEnumerable documentIds) { - if (!task.IsCanceled) _logger.LogDebug($"Timeout before work got ready for {description}."); + if (!task.IsCanceled) _logger.LogDebug($"Timeout before work got ready for one of documents {string.Join(",", documentIds)}."); } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index eb6d112706..29d8a347de 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -138,19 +138,21 @@ private async Task ProcessNextItem(string filePath) }; } - public void QueueForDiagnosis(ImmutableArray documents) + public ImmutableArray QueueForDiagnosis(ImmutableArray documentPaths) { - this.EmitDiagnostics(documents.Select(x => x.FilePath).ToArray()); + this.EmitDiagnostics(documentPaths.ToArray()); + return ImmutableArray.Empty; } - public async Task> GetDiagnostics(ImmutableArray documents) + public async Task> GetDiagnostics(ImmutableArray documentPaths) { - if (documents == null || !documents.Any()) return ImmutableArray<(string projectName, Diagnostic diagnostic)>.Empty; + if (!documentPaths.Any()) return ImmutableArray<(string projectName, Diagnostic diagnostic)>.Empty; var results = new List<(string projectName, Diagnostic diagnostic)>(); - foreach(var document in documents) + foreach(var documentPath in documentPaths) { + var document = _workspace.GetDocument(documentPath); var projectName = document.Project.Name; var diagnostics = await GetDiagnosticsForDocument(document, projectName); results.AddRange(diagnostics.Select(x => (projectName: document.Project.Name, diagnostic: x))); @@ -174,5 +176,17 @@ private static async Task> GetDiagnosticsForDocument( return semanticModel.GetDiagnostics(); } } + + public ImmutableArray QueueAllDocumentsForDiagnostics() + { + var documents = _workspace.CurrentSolution.Projects.SelectMany(x => x.Documents).ToImmutableArray(); + QueueForDiagnosis(documents.Select(x => x.FilePath).ToImmutableArray()); + return documents.Select(x => x.Id).ToImmutableArray(); + } + + public Task> GetAllDiagnostics() + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index f014c9b279..b3834efd68 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -76,43 +76,71 @@ private Task InitializeWithWorkspaceDocumentsIfNotYetDone() .ContinueWith(_ => Task.Delay(50)) .ContinueWith(_ => { - var documents = _workspace.CurrentSolution.Projects.SelectMany(x => x.Documents).ToImmutableArray(); - QueueForAnalysis(documents); - _logger.LogInformation($"Solution initialized -> queue all documents for code analysis. Initial document count: {documents.Length}."); + var documentIds = QueueAllDocumentsForDiagnostics(); + _logger.LogInformation($"Solution initialized -> queue all documents for code analysis. Initial document count: {documentIds.Length}."); }); } - public void QueueForDiagnosis(ImmutableArray documents) + public ImmutableArray QueueForDiagnosis(ImmutableArray documentPaths) { - QueueForAnalysis(documents); + var documentIds = GetDocumentIdsFromPaths(documentPaths); + QueueForAnalysis(documentIds); + return documentIds; } - public async Task> GetDiagnostics(ImmutableArray documents) + public async Task> GetDiagnostics(ImmutableArray documentPaths) { await InitializeWithWorkspaceDocumentsIfNotYetDone(); - await _workQueue.WaitForResultsAsync(documents); + var documentIds = GetDocumentIdsFromPaths(documentPaths); + + return await GetDiagnosticsByDocumentIds(documentIds); + } + + private async Task> GetDiagnosticsByDocumentIds(ImmutableArray documentIds) + { + await _workQueue.WaitForResultsAsync(documentIds); return _results - .Where(x => documents.Any(doc => doc.Id == x.Key)) + .Where(x => documentIds.Any(docId => docId == x.Key)) .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.projectName, v))) .ToImmutableArray(); } + private ImmutableArray GetDocumentIdsFromPaths(ImmutableArray documentPaths) + { + return documentPaths + .Select(docPath => _workspace.GetDocumentId(docPath)) + .ToImmutableArray(); + } + private async Task Worker() { while (true) { try { + var solution = _workspace.CurrentSolution; + var currentWorkGroupedByProjects = _workQueue .TakeWork() - .GroupBy(x => x.Project) + .Select(documentId => (projectId: solution.GetDocument(documentId).Project.Id, documentId)) + .GroupBy(x => x.projectId, x => x.documentId) .ToImmutableArray(); foreach (var projectGroup in currentWorkGroupedByProjects) { - await Analyze(projectGroup.Key, projectGroup.ToImmutableArray()); + // TODO: This should be moved that project rulesets are updated + // to workspace projects itself when project is updated/loaded/manipulated and so on. + // It also causes these inderictions and multiple steps to collect work with projects / documents. + var projectOriginal = solution.GetProject(projectGroup.Key); + + var projectWithOptions = projectOriginal.WithCompilationOptions( + _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(projectOriginal)); + + var projectDocuments = projectGroup.Select(docId => projectWithOptions.GetDocument(docId)).ToImmutableArray(); + + await Analyze(projectWithOptions, projectDocuments); } await Task.Delay(50); @@ -124,9 +152,9 @@ private async Task Worker() } } - private void QueueForAnalysis(ImmutableArray documents) + private void QueueForAnalysis(ImmutableArray documentIds) { - foreach (var document in documents) + foreach (var document in documentIds) { _workQueue.PutWork(document); } @@ -138,7 +166,7 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv || changeEvent.Kind == WorkspaceChangeKind.DocumentRemoved || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded) { - QueueForAnalysis(ImmutableArray.Create(_workspace.CurrentSolution.GetDocument(changeEvent.DocumentId))); + QueueForAnalysis(ImmutableArray.Create(changeEvent.DocumentId)); } } @@ -151,8 +179,7 @@ private async Task Analyze(Project project, ImmutableArray projectDocu .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language))) .ToImmutableArray(); - var compiled = await project.WithCompilationOptions( - _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(project)) + var compiled = await project .GetCompilationAsync(); var workspaceAnalyzerOptions = @@ -173,6 +200,8 @@ private async Task AnalyzeDocument(Project project, ImmutableArray diagnosticsWithAnalyzers) { _results[document.Id] = (project.Name, diagnosticsWithAnalyzers); - _workQueue.MarkWorkAsCompleteForDocument(document); + _workQueue.MarkWorkAsCompleteForDocumentId(document.Id); EmitDiagnostics(_results[document.Id].diagnostics); } @@ -231,5 +267,18 @@ private void EmitDiagnostics(ImmutableArray results) }); } } + + public ImmutableArray QueueAllDocumentsForDiagnostics() + { + var documentIds = _workspace.CurrentSolution.Projects.SelectMany(x => x.DocumentIds).ToImmutableArray(); + QueueForAnalysis(documentIds); + return documentIds; + } + + public Task> GetAllDiagnostics() + { + var allDocumentsIds = _workspace.CurrentSolution.Projects.SelectMany(x => x.DocumentIds).ToImmutableArray(); + return GetDiagnosticsByDocumentIds(allDocumentsIds); + } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs index a310807e9f..7bfbefe597 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs @@ -26,6 +26,7 @@ namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics public class CsharpDiagnosticWorkerComposer: ICsDiagnosticWorker { private readonly ICsDiagnosticWorker _implementation; + private readonly OmniSharpWorkspace _workspace; [ImportingConstructor] public CsharpDiagnosticWorkerComposer( @@ -44,16 +45,28 @@ public CsharpDiagnosticWorkerComposer( { _implementation = new CSharpDiagnosticWorker(workspace, forwarder, loggerFactory); } + + _workspace = workspace; + } + + public Task> GetAllDiagnostics() + { + return _implementation.GetAllDiagnostics(); + } + + public Task> GetDiagnostics(ImmutableArray documentPaths) + { + return _implementation.GetDiagnostics(documentPaths); } - public Task> GetDiagnostics(ImmutableArray documents) + public ImmutableArray QueueAllDocumentsForDiagnostics() { - return _implementation.GetDiagnostics(documents); + return _implementation.QueueAllDocumentsForDiagnostics(); } - public void QueueForDiagnosis(ImmutableArray documents) + public ImmutableArray QueueForDiagnosis(ImmutableArray documentPaths) { - _implementation.QueueForDiagnosis(documents); + return _implementation.QueueForDiagnosis(documentPaths); } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs index fa145eaff1..5d8a210f39 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs @@ -6,7 +6,9 @@ namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics { public interface ICsDiagnosticWorker { - Task> GetDiagnostics(ImmutableArray documents); - void QueueForDiagnosis(ImmutableArray documents); + Task> GetDiagnostics(ImmutableArray documentPaths); + Task> GetAllDiagnostics(); + ImmutableArray QueueForDiagnosis(ImmutableArray documentsPaths); + ImmutableArray QueueAllDocumentsForDiagnostics(); } } \ No newline at end of file diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs index 8eab74258b..8e6aaa85f6 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/AnalyzerWorkerQueueFacts.cs @@ -51,7 +51,7 @@ public void WhenItemsAreAddedButThrotlingIsntOverNoWorkShouldBeReturned() { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocument(); + var document = CreateTestDocumentId(); queue.PutWork(document); Assert.Empty(queue.TakeWork()); @@ -62,7 +62,7 @@ public void WhenWorksIsAddedToQueueThenTheyWillBeReturned() { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocument(); + var document = CreateTestDocumentId(); queue.PutWork(document); @@ -78,7 +78,7 @@ public void WhenSameItemIsAddedMultipleTimesInRowThenThrottleItemAsOne() { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now); - var document = CreateTestDocument(); + var document = CreateTestDocumentId(); queue.PutWork(document); queue.PutWork(document); @@ -99,7 +99,7 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); - var document = CreateTestDocument(); + var document = CreateTestDocumentId(); queue.PutWork(document); @@ -111,7 +111,7 @@ public void WhenWorkIsAddedThenWaitNextIterationOfItReady() now = PassOverThrotlingPeriod(now); var work = queue.TakeWork(); - queue.MarkWorkAsCompleteForDocument(document); + queue.MarkWorkAsCompleteForDocumentId(document); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -121,7 +121,7 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR { var now = DateTime.UtcNow; var queue = new AnalyzerWorkQueue(new LoggerFactory(), utcNow: () => now, timeoutForPendingWorkMs: 500); - var document = CreateTestDocument(); + var document = CreateTestDocumentId(); queue.PutWork(document); @@ -133,7 +133,7 @@ public void WhenWorkIsUnderAnalysisOutFromQueueThenWaitUntilNextIterationOfItIsR pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.False(pendingTask.IsCompleted); - queue.MarkWorkAsCompleteForDocument(document); + queue.MarkWorkAsCompleteForDocumentId(document); pendingTask.Wait(TimeSpan.FromMilliseconds(50)); Assert.True(pendingTask.IsCompleted); } @@ -144,7 +144,7 @@ public void WhenWorkIsWaitedButTimeoutForWaitIsExceededAllowContinue() var now = DateTime.UtcNow; var loggerFactory = new LoggerFactory(); var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now, timeoutForPendingWorkMs: 20); - var document = CreateTestDocument(); + var document = CreateTestDocumentId(); queue.PutWork(document); @@ -169,7 +169,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp Enumerable.Range(0, 10) .Select(_ => Task.Run(() => { - var document = CreateTestDocument(); + var document = CreateTestDocumentId(); queue.PutWork(document); @@ -180,7 +180,7 @@ public async Task WhenMultipleThreadsAreConsumingAnalyzerWorkerQueueItWorksAsExp foreach (var workDoc in work) { - queue.MarkWorkAsCompleteForDocument(workDoc); + queue.MarkWorkAsCompleteForDocumentId(workDoc); } pendingTask.Wait(TimeSpan.FromMilliseconds(300)); @@ -198,7 +198,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe var now = DateTime.UtcNow; var loggerFactory = new LoggerFactory(); var queue = new AnalyzerWorkQueue(loggerFactory, utcNow: () => now); - var document = CreateTestDocument(); + var document = CreateTestDocumentId(); queue.PutWork(document); @@ -213,7 +213,7 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe queue.PutWork(document); // First iteration of work is done. - queue.MarkWorkAsCompleteForDocument(document); + queue.MarkWorkAsCompleteForDocumentId(document); // Waiting call continues because it's iteration of work is done, even when theres next // already waiting. @@ -223,21 +223,16 @@ public async Task WhenWorkIsAddedAgainWhenPreviousIsAnalysing_ThenDontWaitAnothe Assert.Empty(loggerFactory.Logger.RecordedMessages); } - private Document CreateTestDocument() + private DocumentId CreateTestDocumentId() { var projectInfo = ProjectInfo.Create( - id: ProjectId.CreateNewId(), - version: VersionStamp.Create(), - name: "testProject", - assemblyName: "AssemblyName", - language: LanguageNames.CSharp); - - var tempWorkspace = new AdhocWorkspace(); - tempWorkspace.AddProject(projectInfo); - return tempWorkspace.AddDocument(DocumentInfo.Create( - id: DocumentId.CreateNewId(projectInfo.Id), - name: "testFile.cs")); + id: ProjectId.CreateNewId(), + version: VersionStamp.Create(), + name: "testProject", + assemblyName: "AssemblyName", + language: LanguageNames.CSharp); + return DocumentId.CreateNewId(projectInfo.Id); } } } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs index a694774c07..72712fec6f 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsV2Facts.cs @@ -36,7 +36,7 @@ public async Task CodeCheckSpecifiedFileOnly(string filename) var service = CreateDiagnosticService(forwarder); SharedOmniSharpTestHost.AddFilesToWorkspace(testFile); - var controller = new DiagnosticsService(SharedOmniSharpTestHost.Workspace, forwarder, service); + var controller = new DiagnosticsService(forwarder, service); var response = await controller.Handle(new DiagnosticsRequest { FileName = testFile.FileName }); await emitter.ExpectForEmitted(msg => msg.Results.Any(m => m.FileName == filename)); @@ -62,7 +62,7 @@ public async Task CheckAllFiles(string filename1, string filename2) var forwarder = new DiagnosticEventForwarder(emitter); var service = CreateDiagnosticService(forwarder); - var controller = new DiagnosticsService(SharedOmniSharpTestHost.Workspace, forwarder, service); + var controller = new DiagnosticsService(forwarder, service); var response = await controller.Handle(new DiagnosticsRequest()); await emitter.ExpectForEmitted(msg => msg.Results @@ -86,7 +86,7 @@ public async Task EnablesWhenEndPointIsHit(string filename1, string filename2) var forwarder = new DiagnosticEventForwarder(emitter); var service = CreateDiagnosticService(forwarder); - var controller = new DiagnosticsService(SharedOmniSharpTestHost.Workspace, forwarder, service); + var controller = new DiagnosticsService(forwarder, service); var response = await controller.Handle(new DiagnosticsRequest()); Assert.True(forwarder.IsEnabled); From 2a0cb3ed1719790a557bd050550b838933f93427 Mon Sep 17 00:00:00 2001 From: Savpek Date: Tue, 26 Feb 2019 22:11:58 +0200 Subject: [PATCH 168/178] Initial fix for rulesets with syntax analysis. --- .../Services/Diagnostics/CodeCheckService.cs | 8 ++------ .../Diagnostics/CSharpDiagnosticWorker.cs | 6 +++--- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 17 ++++++++++------- .../CsharpDiagnosticWorkerComposer.cs | 4 ++-- .../Workers/Diagnostics/ICsDiagnosticWorker.cs | 2 +- .../CodeActionsV2Facts.cs | 15 ++++++++++++++- .../CustomRoslynAnalyzerFacts.cs | 8 +++----- 7 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs index 2166d8cfeb..e0e526921d 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/CodeCheckService.cs @@ -18,8 +18,6 @@ namespace OmniSharp.Roslyn.CSharp.Services.Diagnostics [OmniSharpHandler(OmniSharpEndpoints.CodeCheck, LanguageNames.CSharp)] public class CodeCheckService : IRequestHandler { - private readonly OmniSharpWorkspace _workspace; - private readonly OmniSharpOptions _options; private readonly ICsDiagnosticWorker _diagWorker; private readonly ILogger _logger; @@ -30,17 +28,15 @@ public CodeCheckService( OmniSharpOptions options, ICsDiagnosticWorker diagWorker) { - _workspace = workspace; - _options = options; _diagWorker = diagWorker; _logger = loggerFactory.CreateLogger(); } public async Task Handle(CodeCheckRequest request) { - if (request.FileName == null) + if (string.IsNullOrEmpty(request.FileName)) { - var allDiagnostics = await _diagWorker.GetAllDiagnostics(); + var allDiagnostics = await _diagWorker.GetAllDiagnosticsAsync(); return GetResponseFromDiagnostics(allDiagnostics, fileName: null); } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 29d8a347de..3b1dccf23f 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -29,7 +29,6 @@ public class CSharpDiagnosticWorker: ICsDiagnosticWorker { private readonly ILogger _logger; private readonly OmniSharpWorkspace _workspace; - private readonly object _lock = new object(); private readonly DiagnosticEventForwarder _forwarder; private readonly IObserver _openDocuments; @@ -184,9 +183,10 @@ public ImmutableArray QueueAllDocumentsForDiagnostics() return documents.Select(x => x.Id).ToImmutableArray(); } - public Task> GetAllDiagnostics() + public Task> GetAllDiagnosticsAsync() { - throw new NotImplementedException(); + var documents = _workspace.CurrentSolution.Projects.SelectMany(x => x.Documents).Select(x => x.FilePath).ToImmutableArray(); + return GetDiagnostics(documents); } } } \ No newline at end of file diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index b3834efd68..356b72d3a6 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -110,8 +110,8 @@ public ImmutableArray QueueForDiagnosis(ImmutableArray docum private ImmutableArray GetDocumentIdsFromPaths(ImmutableArray documentPaths) { return documentPaths - .Select(docPath => _workspace.GetDocumentId(docPath)) - .ToImmutableArray(); + .Select(docPath => _workspace.GetDocumentId(docPath)) + .ToImmutableArray(); } private async Task Worker() @@ -140,7 +140,7 @@ private async Task Worker() var projectDocuments = projectGroup.Select(docId => projectWithOptions.GetDocument(docId)).ToImmutableArray(); - await Analyze(projectWithOptions, projectDocuments); + await AnalyzeProject(projectWithOptions, projectDocuments); } await Task.Delay(50); @@ -164,13 +164,15 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv { if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged || changeEvent.Kind == WorkspaceChangeKind.DocumentRemoved - || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded) + || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded + || changeEvent.Kind == WorkspaceChangeKind.DocumentReloaded + || changeEvent.Kind == WorkspaceChangeKind.DocumentInfoChanged ) { QueueForAnalysis(ImmutableArray.Create(changeEvent.DocumentId)); } } - private async Task Analyze(Project project, ImmutableArray projectDocuments) + private async Task AnalyzeProject(Project project, ImmutableArray projectDocuments) { try { @@ -275,10 +277,11 @@ public ImmutableArray QueueAllDocumentsForDiagnostics() return documentIds; } - public Task> GetAllDiagnostics() + public async Task> GetAllDiagnosticsAsync() { + await InitializeWithWorkspaceDocumentsIfNotYetDone(); var allDocumentsIds = _workspace.CurrentSolution.Projects.SelectMany(x => x.DocumentIds).ToImmutableArray(); - return GetDiagnosticsByDocumentIds(allDocumentsIds); + return await GetDiagnosticsByDocumentIds(allDocumentsIds); } } } diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs index 7bfbefe597..5930a2c125 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CsharpDiagnosticWorkerComposer.cs @@ -49,9 +49,9 @@ public CsharpDiagnosticWorkerComposer( _workspace = workspace; } - public Task> GetAllDiagnostics() + public Task> GetAllDiagnosticsAsync() { - return _implementation.GetAllDiagnostics(); + return _implementation.GetAllDiagnosticsAsync(); } public Task> GetDiagnostics(ImmutableArray documentPaths) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs index 5d8a210f39..3295efa68b 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/ICsDiagnosticWorker.cs @@ -7,7 +7,7 @@ namespace OmniSharp.Roslyn.CSharp.Workers.Diagnostics public interface ICsDiagnosticWorker { Task> GetDiagnostics(ImmutableArray documentPaths); - Task> GetAllDiagnostics(); + Task> GetAllDiagnosticsAsync(); ImmutableArray QueueForDiagnosis(ImmutableArray documentsPaths); ImmutableArray QueueAllDocumentsForDiagnostics(); } diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs index 8e7165d888..ccaf723162 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CodeActionsV2Facts.cs @@ -126,7 +126,20 @@ public void Whatever() var refactorings = await FindRefactoringNamesAsync(code, roslynAnalyzersEnabled); - List expected = new List + List expected = roslynAnalyzersEnabled ? new List + { + "Fix formatting", + "using System;", + "System.Console", + "Generate variable 'Console' -> Generate property 'Class1.Console'", + "Generate variable 'Console' -> Generate field 'Class1.Console'", + "Generate variable 'Console' -> Generate read-only field 'Class1.Console'", + "Generate variable 'Console' -> Generate local 'Console'", + "Generate type 'Console' -> Generate class 'Console' in new file", + "Generate type 'Console' -> Generate class 'Console'", + "Generate type 'Console' -> Generate nested class 'Console'", + "Extract Method" + } : new List { "using System;", "System.Console", diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index 8f1529276c..d80fdeb4a2 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -55,7 +55,7 @@ public TestDiagnosticAnalyzer(string id, bool isEnabledByDefault) "Testtitle", "Type name '{0}' contains lowercase letters", "Naming", - DiagnosticSeverity.Warning, + DiagnosticSeverity.Error, isEnabledByDefault: _isEnabledByDefault ); @@ -102,11 +102,11 @@ public async Task When_custom_analyzers_are_executed_then_return_results() host.AddFilesToWorkspace(testFile); - var testAnalyzerRef = new TestAnalyzerReference("TS1234"); + var testAnalyzerRef = new TestAnalyzerReference("TS1234", isEnabledByDefault: true); AddProjectWitFile(host, testFile, testAnalyzerRef); - var result = await host.RequestCodeCheckAsync("testFile.cs"); + var result = await host.RequestCodeCheckAsync(); Assert.Contains(result.QuickFixes, f => f.Text.Contains(testAnalyzerRef.Id.ToString())); } } @@ -128,8 +128,6 @@ public async Task Always_return_results_from_net_default_analyzers() var result = await host.RequestCodeCheckAsync(); - result = await host.RequestCodeCheckAsync(); - Assert.Contains(result.QuickFixes.Where(x => x.FileName == testFile.FileName), f => f.Text.Contains("CS")); } } From 0c28cbb08e4848669e0b270aff2348661f2dfc79 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 27 Feb 2019 08:08:48 +0200 Subject: [PATCH 169/178] Moved comment to correct location. --- .../Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 356b72d3a6..41ed47ce44 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -218,9 +218,8 @@ private async Task AnalyzeDocument(Project project, ImmutableArray Date: Wed, 27 Feb 2019 18:12:57 +0200 Subject: [PATCH 170/178] Logging improvement. --- .../Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 41ed47ce44..8c2c611790 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -242,7 +242,7 @@ private async Task AnalyzeDocument(Project project, ImmutableArray Date: Wed, 27 Feb 2019 18:36:38 +0200 Subject: [PATCH 171/178] Test for CS rule apply. --- .../CustomRoslynAnalyzerFacts.cs | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index d80fdeb4a2..3b49c8051d 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -132,6 +132,40 @@ public async Task Always_return_results_from_net_default_analyzers() } } + [Fact] + public async Task Rulesets_should_work_with_syntax_analyzers() + { + using (var host = GetHost()) + { + var testFile = new TestFile("testFile_9.cs", @" + class Program + { + static void Main(string[] args) + { + return; + Console.WriteLine(null); // This is CS0162, unreachable code. + } + }"); + var ruleService = host.GetExport(); + + var projectIds = AddProjectWitFile(host, testFile); + + var testRules = CreateRules("CS0162", ReportDiagnostic.Hidden); + + ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( + "", + new ReportDiagnostic(), + testRules.ToImmutableDictionary(), + new ImmutableArray())); + + var result = await host.RequestCodeCheckAsync(); + + var foo = result.QuickFixes.ToList(); + + Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains("CS0162") && f.LogLevel == "Hidden"); + } + } + [Fact] public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_severity() { @@ -143,7 +177,7 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ var testAnalyzerRef = new TestAnalyzerReference("TS1100"); var projectIds = AddProjectWitFile(host, testFile, testAnalyzerRef); - var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Hidden); + var testRules = CreateRules(testAnalyzerRef.Id.ToString(), ReportDiagnostic.Hidden); ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( "", @@ -156,11 +190,11 @@ public async Task When_rules_udpate_diagnostic_severity_then_show_them_with_new_ } } - private static Dictionary CreateRules(TestAnalyzerReference testAnalyzerRef, ReportDiagnostic diagnostic) + private static Dictionary CreateRules(string analyzerId, ReportDiagnostic diagnostic) { return new Dictionary { - { testAnalyzerRef.Id.ToString(), diagnostic } + { analyzerId, diagnostic } }; } @@ -178,7 +212,7 @@ public async Task When_custom_rule_is_set_to_none_dont_return_results_at_all() var projectIds = AddProjectWitFile(host, testFile, testAnalyzerRef); - var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Suppress); + var testRules = CreateRules(testAnalyzerRef.Id.ToString(), ReportDiagnostic.Suppress); ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( "", @@ -203,7 +237,7 @@ public async Task When_diagnostic_is_disabled_by_default_updating_rule_will_enab var projectIds = AddProjectWitFile(host, testFile, testAnalyzerRef); - var testRules = CreateRules(testAnalyzerRef, ReportDiagnostic.Error); + var testRules = CreateRules(testAnalyzerRef.Id.ToString(), ReportDiagnostic.Error); ruleService.AddOrUpdateRuleset(projectIds.Single(), new RuleSet( "", From 2d81bda7ebceb45d8ca11a7860e7716a2251bf31 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 27 Feb 2019 18:41:02 +0200 Subject: [PATCH 172/178] Removed dummy variable. --- .../OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs index 3b49c8051d..99649303e9 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/CustomRoslynAnalyzerFacts.cs @@ -160,8 +160,6 @@ static void Main(string[] args) var result = await host.RequestCodeCheckAsync(); - var foo = result.QuickFixes.ToList(); - Assert.Contains(result.QuickFixes.OfType(), f => f.Text.Contains("CS0162") && f.LogLevel == "Hidden"); } } From 1c83919d55a063d5f54a7ad40be9828f952eac95 Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 27 Feb 2019 19:02:40 +0200 Subject: [PATCH 173/178] Fix for document loading. --- .../Workers/Diagnostics/CSharpDiagnosticWorker.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 3b1dccf23f..79fcdc3ceb 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -151,7 +151,14 @@ public ImmutableArray QueueForDiagnosis(ImmutableArray docum foreach(var documentPath in documentPaths) { - var document = _workspace.GetDocument(documentPath); + // To properly handle the request wait until all projects are loaded + var documents = await _workspace.GetDocumentsFromFullProjectModelAsync(documentPath); + + var document = documents.SingleOrDefault(); + + if(document?.Project?.Name == null) + continue; + var projectName = document.Project.Name; var diagnostics = await GetDiagnosticsForDocument(document, projectName); results.AddRange(diagnostics.Select(x => (projectName: document.Project.Name, diagnostic: x))); @@ -189,4 +196,4 @@ public ImmutableArray QueueAllDocumentsForDiagnostics() return GetDiagnostics(documents); } } -} \ No newline at end of file +} From ce6ca692ea0991879bca21277f804b2de4ef0ffd Mon Sep 17 00:00:00 2001 From: Savpek Date: Wed, 27 Feb 2019 19:41:23 +0200 Subject: [PATCH 174/178] Fixed mistake on document fetching. --- .../Workers/Diagnostics/CSharpDiagnosticWorker.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs index 79fcdc3ceb..28f1e5db70 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorker.cs @@ -149,13 +149,14 @@ public ImmutableArray QueueForDiagnosis(ImmutableArray docum var results = new List<(string projectName, Diagnostic diagnostic)>(); - foreach(var documentPath in documentPaths) - { - // To properly handle the request wait until all projects are loaded - var documents = await _workspace.GetDocumentsFromFullProjectModelAsync(documentPath); - - var document = documents.SingleOrDefault(); + var documents = + (await Task.WhenAll( + documentPaths + .Select(docPath => _workspace.GetDocumentsFromFullProjectModelAsync(docPath))) + ).SelectMany(s => s); + foreach (var document in documents) + { if(document?.Project?.Name == null) continue; From 7e4b77c86410c232656418532694938da0f359e6 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 28 Feb 2019 17:18:14 +0200 Subject: [PATCH 175/178] Attempt to fix duplicate key error. --- .../Services/Diagnostics/RulesetsForProjects.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs index a6247c6a31..98d186ea92 100644 --- a/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs +++ b/src/OmniSharp.Roslyn.CSharp/Services/Diagnostics/RulesetsForProjects.cs @@ -26,7 +26,11 @@ public CompilationOptions BuildCompilationOptionsWithCurrentRules(Project projec return project.CompilationOptions; var existingRules = project.CompilationOptions.SpecificDiagnosticOptions; - return project.CompilationOptions.WithSpecificDiagnosticOptions(existingRules.Concat(GetRules(project.Id))); + var projectRules = GetRules(project.Id); + + var distinctRulesWithProjectSpecificRules = projectRules.Concat(existingRules.Where( x=> !projectRules.Keys.Contains(x.Key))); + + return project.CompilationOptions.WithSpecificDiagnosticOptions(distinctRulesWithProjectSpecificRules); } public void AddOrUpdateRuleset(ProjectId projectId, RuleSet ruleset) From 583d68aa3b44e427fed4cbf6612be4a049610d23 Mon Sep 17 00:00:00 2001 From: Savpek Date: Thu, 28 Feb 2019 22:34:03 +0200 Subject: [PATCH 176/178] Small refactoring. --- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 8c2c611790..6edc81f5d7 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -130,17 +130,7 @@ private async Task Worker() foreach (var projectGroup in currentWorkGroupedByProjects) { - // TODO: This should be moved that project rulesets are updated - // to workspace projects itself when project is updated/loaded/manipulated and so on. - // It also causes these inderictions and multiple steps to collect work with projects / documents. - var projectOriginal = solution.GetProject(projectGroup.Key); - - var projectWithOptions = projectOriginal.WithCompilationOptions( - _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(projectOriginal)); - - var projectDocuments = projectGroup.Select(docId => projectWithOptions.GetDocument(docId)).ToImmutableArray(); - - await AnalyzeProject(projectWithOptions, projectDocuments); + await AnalyzeProject(solution, projectGroup); } await Task.Delay(50); @@ -172,29 +162,38 @@ private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEv } } - private async Task AnalyzeProject(Project project, ImmutableArray projectDocuments) + private async Task AnalyzeProject(Solution solution, IGrouping documentsGroupedByProject) { try { + // TODO: This should be moved that project rulesets are updated + // to workspace projects itself when project is updated/loaded/manipulated and so on. + // It also causes these inderictions and multiple steps to collect work with projects / documents. + var projectOriginal = solution.GetProject(documentsGroupedByProject.Key); + + var projectWithOptions = projectOriginal.WithCompilationOptions( + _rulesetsForProjects.BuildCompilationOptionsWithCurrentRules(projectOriginal)); + var allAnalyzers = _providers .SelectMany(x => x.CodeDiagnosticAnalyzerProviders) - .Concat(project.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(project.Language))) + .Concat(projectWithOptions.AnalyzerReferences.SelectMany(x => x.GetAnalyzers(projectWithOptions.Language))) .ToImmutableArray(); - var compiled = await project + var compiled = await projectWithOptions .GetCompilationAsync(); var workspaceAnalyzerOptions = - (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { project.AnalyzerOptions, project.Solution.Options, project.Solution }); + (AnalyzerOptions)_workspaceAnalyzerOptionsConstructor.Invoke(new object[] { projectWithOptions.AnalyzerOptions, projectWithOptions.Solution.Options, projectWithOptions.Solution }); - foreach (var document in projectDocuments) + foreach (var documentId in documentsGroupedByProject) { - await AnalyzeDocument(project, allAnalyzers, compiled, workspaceAnalyzerOptions, document); + var document = projectWithOptions.GetDocument(documentId); + await AnalyzeDocument(projectWithOptions, allAnalyzers, compiled, workspaceAnalyzerOptions, document); } } catch (Exception ex) { - _logger.LogError($"Analysis of project {project.Id} ({project.Name}) failed, underlaying error: {ex}"); + _logger.LogError($"Analysis of project {documentsGroupedByProject.Key} failed, underlaying error: {ex}"); } } From 5abc746e2021a75b0aa06eef595ed51eac7d64c7 Mon Sep 17 00:00:00 2001 From: Savpek Date: Fri, 1 Mar 2019 08:09:06 +0200 Subject: [PATCH 177/178] Removed unnecessary semantic model get. --- .../Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index 6edc81f5d7..fc66e00284 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -201,9 +201,7 @@ private async Task AnalyzeDocument(Project project, ImmutableArray Date: Thu, 14 Mar 2019 21:34:30 +0200 Subject: [PATCH 178/178] Fix for null ref error on document removal. --- .../CSharpDiagnosticWorkerWithAnalyzers.cs | 16 +++++++++----- .../DiagnosticsFacts.cs | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs index fc66e00284..d287cf4ac7 100644 --- a/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs +++ b/src/OmniSharp.Roslyn.CSharp/Workers/Diagnostics/CSharpDiagnosticWorkerWithAnalyzers.cs @@ -23,7 +23,7 @@ public class CSharpDiagnosticWorkerWithAnalyzers : ICsDiagnosticWorker { private readonly AnalyzerWorkQueue _workQueue; private readonly ILogger _logger; - private readonly ConcurrentDictionary diagnostics)> _results = + private readonly ConcurrentDictionary diagnostics)> _currentDiagnosticResults = new ConcurrentDictionary diagnostics)>(); private readonly ImmutableArray _providers; private readonly DiagnosticEventForwarder _forwarder; @@ -101,7 +101,7 @@ public ImmutableArray QueueForDiagnosis(ImmutableArray docum { await _workQueue.WaitForResultsAsync(documentIds); - return _results + return _currentDiagnosticResults .Where(x => documentIds.Any(docId => docId == x.Key)) .SelectMany(x => x.Value.diagnostics, (k, v) => ((k.Value.projectName, v))) .ToImmutableArray(); @@ -124,7 +124,8 @@ private async Task Worker() var currentWorkGroupedByProjects = _workQueue .TakeWork() - .Select(documentId => (projectId: solution.GetDocument(documentId).Project.Id, documentId)) + .Select(documentId => (projectId: solution.GetDocument(documentId)?.Project?.Id, documentId)) + .Where(x => x.projectId != null) .GroupBy(x => x.projectId, x => x.documentId) .ToImmutableArray(); @@ -153,13 +154,16 @@ private void QueueForAnalysis(ImmutableArray documentIds) private void OnWorkspaceChanged(object sender, WorkspaceChangeEventArgs changeEvent) { if (changeEvent.Kind == WorkspaceChangeKind.DocumentChanged - || changeEvent.Kind == WorkspaceChangeKind.DocumentRemoved || changeEvent.Kind == WorkspaceChangeKind.DocumentAdded || changeEvent.Kind == WorkspaceChangeKind.DocumentReloaded || changeEvent.Kind == WorkspaceChangeKind.DocumentInfoChanged ) { QueueForAnalysis(ImmutableArray.Create(changeEvent.DocumentId)); } + else if(changeEvent.Kind == WorkspaceChangeKind.DocumentRemoved) + { + _currentDiagnosticResults.TryRemove(changeEvent.DocumentId, out _); + } } private async Task AnalyzeProject(Solution solution, IGrouping documentsGroupedByProject) @@ -246,9 +250,9 @@ private async Task AnalyzeDocument(Project project, ImmutableArray diagnosticsWithAnalyzers) { - _results[document.Id] = (project.Name, diagnosticsWithAnalyzers); + _currentDiagnosticResults[document.Id] = (project.Name, diagnosticsWithAnalyzers); _workQueue.MarkWorkAsCompleteForDocumentId(document.Id); - EmitDiagnostics(_results[document.Id].diagnostics); + EmitDiagnostics(_currentDiagnosticResults[document.Id].diagnostics); } private void EmitDiagnostics(ImmutableArray results) diff --git a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs index 8806a476e9..e62f6a301b 100644 --- a/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs +++ b/tests/OmniSharp.Roslyn.CSharp.Tests/DiagnosticsFacts.cs @@ -58,6 +58,28 @@ public async Task CheckAllFiles(bool roslynAnalyzersEnabled) } } + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task WhenFileIsDeletedFromWorkSpaceThenResultsAreNotReturnedAnyMore(bool roslynAnalyzersEnabled) + { + using(var host = GetHost(roslynAnalyzersEnabled)) + { + host.AddFilesToWorkspace(new TestFile("a.cs", "class C1 { int n = true; }")); + await host.RequestCodeCheckAsync(); + + foreach (var doc in host.Workspace.CurrentSolution.Projects.SelectMany(x => x.Documents)) + { + // Theres document for each targeted framework, lets delete all. + host.Workspace.RemoveDocument(doc.Id); + } + + var quickFixes = await host.RequestCodeCheckAsync(); + + Assert.DoesNotContain(quickFixes.QuickFixes, x => x.Text.Contains("CS0029") && x.FileName == "a.cs"); + } + } + [Fact] public async Task AnalysisSupportBuiltInIDEAnalysers() {