diff --git a/src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs b/src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs index 098a801..319c01e 100644 --- a/src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs +++ b/src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using System.ComponentModel; using System.Diagnostics; +using System.Collections.Concurrent; #if NETCOREAPP using System.Runtime.Loader; @@ -133,27 +134,27 @@ public enum BasicAnalyzerKind /// public abstract class BasicAnalyzerHost : IDisposable { - private readonly BasicAnalyzerKind _kind; - private readonly ImmutableArray _analyzerReferences; + private readonly ConcurrentQueue _diagnostics = new(); - public BasicAnalyzerKind Kind => _kind; + public BasicAnalyzerHostOptions Options { get; } + public BasicAnalyzerKind Kind { get; } public ImmutableArray AnalyzerReferences { get { CheckDisposed(); - return _analyzerReferences; + return AnalyzerReferencesCore; } } + protected abstract ImmutableArray AnalyzerReferencesCore { get; } + public bool IsDisposed { get; private set; } - protected BasicAnalyzerHost( - BasicAnalyzerKind kind, - ImmutableArray analyzerReferences) + protected BasicAnalyzerHost(BasicAnalyzerKind kind, BasicAnalyzerHostOptions options) { - _kind = kind; - _analyzerReferences = analyzerReferences; + Kind = kind; + Options = options; } public void Dispose() @@ -183,6 +184,15 @@ protected void CheckDisposed() } } + protected void AddDiagnostic(Diagnostic diagnostic) => _diagnostics.Enqueue(diagnostic); + + /// + /// Get the current set of diagnostics. This can change as analyzers can add them during + /// execution which can happen in parallel to analysis. + /// + public List GetDiagnostics() => _diagnostics.ToList(); + + public static bool IsSupported(BasicAnalyzerKind kind) { #if NETCOREAPP diff --git a/src/Basic.CompilerLog.Util/CompilationData.cs b/src/Basic.CompilerLog.Util/CompilationData.cs index 433fa8a..4bc972b 100644 --- a/src/Basic.CompilerLog.Util/CompilationData.cs +++ b/src/Basic.CompilerLog.Util/CompilationData.cs @@ -90,6 +90,13 @@ public Compilation GetCompilationAfterGenerators(out ImmutableArray driver.RunGeneratorsAndUpdateCompilation(Compilation, out tuple.Item1, out tuple.Item2, cancellationToken); _afterGenerators = tuple; diagnostics = tuple.Item2; + + // Now that analyzers have completed running add any diagnostics the host has captured + if (BasicAnalyzerHost.GetDiagnostics() is { Count: > 0} list) + { + diagnostics = diagnostics.AddRange(list); + } + return tuple.Item1; } diff --git a/src/Basic.CompilerLog.Util/CompilerLogReader.cs b/src/Basic.CompilerLog.Util/CompilerLogReader.cs index a9448c0..1892a82 100644 --- a/src/Basic.CompilerLog.Util/CompilerLogReader.cs +++ b/src/Basic.CompilerLog.Util/CompilerLogReader.cs @@ -557,9 +557,9 @@ internal BasicAnalyzerHost ReadAnalyzers(RawCompilationData rawCompilationData) basicAnalyzerHost = BasicAnalyzerHostOptions.ResolvedKind switch { - BasicAnalyzerKind.OnDisk => BasicAnalyzerHostOnDisk.Create(this, analyzers, BasicAnalyzerHostOptions), - BasicAnalyzerKind.InMemory => BasicAnalyzerHostInMemory.Create(this, analyzers, BasicAnalyzerHostOptions), - BasicAnalyzerKind.None => new BasicAnalyzerHostNone(rawCompilationData.ReadGeneratedFiles, ReadGeneratedSourceTexts()), + BasicAnalyzerKind.OnDisk => new BasicAnalyzerHostOnDisk(this, analyzers, BasicAnalyzerHostOptions), + BasicAnalyzerKind.InMemory => new BasicAnalyzerHostInMemory(this, analyzers, BasicAnalyzerHostOptions), + BasicAnalyzerKind.None => new BasicAnalyzerHostNone(rawCompilationData.ReadGeneratedFiles, ReadGeneratedSourceTexts(), BasicAnalyzerHostOptions), _ => throw new InvalidOperationException() }; diff --git a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs index f20cc79..832a1a1 100644 --- a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs +++ b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs @@ -18,20 +18,13 @@ namespace Basic.CompilerLog.Util.Impl; internal sealed class BasicAnalyzerHostInMemory : BasicAnalyzerHost { internal InMemoryLoader Loader { get; } + protected override ImmutableArray AnalyzerReferencesCore => Loader.AnalyzerReferences; - private BasicAnalyzerHostInMemory( - InMemoryLoader loader, - ImmutableArray analyzerReferences) - : base(BasicAnalyzerKind.InMemory, analyzerReferences) - { - Loader = loader; - } - - internal static BasicAnalyzerHostInMemory Create(CompilerLogReader reader, List analyzers, BasicAnalyzerHostOptions options) + internal BasicAnalyzerHostInMemory(CompilerLogReader reader, List analyzers, BasicAnalyzerHostOptions options) + :base(BasicAnalyzerKind.InMemory, options) { var name = $"{nameof(BasicAnalyzerHostInMemory)} - {Guid.NewGuid().ToString("N")}"; - var loader = new InMemoryLoader(name, options, reader, analyzers); - return new BasicAnalyzerHostInMemory(loader, loader.AnalyzerReferences); + Loader = new InMemoryLoader(name, options, reader, analyzers, AddDiagnostic); } protected override void DisposeCore() @@ -48,7 +41,7 @@ internal sealed class InMemoryLoader : AssemblyLoadContext internal ImmutableArray AnalyzerReferences { get; } internal AssemblyLoadContext CompilerLoadContext { get; } - internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List analyzers) + internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List analyzers, Action onDiagnostic) :base(name, isCollectible: true) { CompilerLoadContext = options.CompilerLoadContext; @@ -57,7 +50,7 @@ internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerL { var simpleName = Path.GetFileNameWithoutExtension(analyzer.FileName); _map[simpleName] = reader.GetAssemblyBytes(analyzer.Mvid); - builder.Add(new BasicAnalyzerReference(new AssemblyName(simpleName), this)); + builder.Add(new BasicAnalyzerReference(new AssemblyName(simpleName), this, onDiagnostic)); } AnalyzerReferences = builder.MoveToImmutable(); @@ -99,7 +92,7 @@ internal sealed class InMemoryLoader { internal ImmutableArray AnalyzerReferences { get; } - internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List analyzers) + internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List analyzers, Action onDiagnostic) { throw new PlatformNotSupportedException(); } @@ -200,15 +193,26 @@ public void Dispose() file sealed class BasicAnalyzerReference : AnalyzerReference { + public static readonly DiagnosticDescriptor CannotLoadTypes = + new DiagnosticDescriptor( + "BCLA0002", + "Failed to load types from assembly", + "Failed to load types from {0}: {1}", + "BasicCompilerLog", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + internal AssemblyName AssemblyName { get; } internal InMemoryLoader Loader { get; } + internal Action OnDiagnostic { get; } public override object Id { get; } = Guid.NewGuid(); public override string? FullPath => null; - internal BasicAnalyzerReference(AssemblyName assemblyName, InMemoryLoader loader) + internal BasicAnalyzerReference(AssemblyName assemblyName, InMemoryLoader loader, Action onDiagnostic) { AssemblyName = assemblyName; Loader = loader; + OnDiagnostic = onDiagnostic; } public override ImmutableArray GetAnalyzers(string language) @@ -245,10 +249,10 @@ internal Type[] GetTypes(Assembly assembly) { return assembly.GetTypes(); } - catch + catch (Exception ex) { - // TODO: need to handle the load errors here same way as compiler. The CodeFixProvider assemblies - // not loading shouldn't lead to not generating anything. + var diagnostic = Diagnostic.Create(CannotLoadTypes, Location.None, assembly.FullName, ex.Message); + OnDiagnostic(diagnostic); return Array.Empty(); } } diff --git a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs index 2e0b5f8..5967d44 100644 --- a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs +++ b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs @@ -11,9 +11,6 @@ namespace Basic.CompilerLog.Util.Impl; /// internal sealed class BasicAnalyzerHostNone : BasicAnalyzerHost { - internal bool ReadGeneratedFiles { get; } - internal ImmutableArray<(SourceText SourceText, string Path)> GeneratedSourceTexts { get; } - public static readonly DiagnosticDescriptor CannotReadGeneratedFiles = new DiagnosticDescriptor( "BCLA0001", @@ -23,24 +20,24 @@ internal sealed class BasicAnalyzerHostNone : BasicAnalyzerHost DiagnosticSeverity.Warning, isEnabledByDefault: true); - internal BasicAnalyzerHostNone(bool readGeneratedFiles, ImmutableArray<(SourceText SourceText, string Path)> generatedSourceTexts) - : base( - BasicAnalyzerKind.None, - CreateAnalyzerReferences(readGeneratedFiles, generatedSourceTexts)) + internal bool ReadGeneratedFiles { get; } + internal ImmutableArray<(SourceText SourceText, string Path)> GeneratedSourceTexts { get; } + protected override ImmutableArray AnalyzerReferencesCore { get; } + + internal BasicAnalyzerHostNone(bool readGeneratedFiles, ImmutableArray<(SourceText SourceText, string Path)> generatedSourceTexts, BasicAnalyzerHostOptions options) + : base(BasicAnalyzerKind.None, options) { ReadGeneratedFiles = readGeneratedFiles; GeneratedSourceTexts = generatedSourceTexts; + AnalyzerReferencesCore = readGeneratedFiles && generatedSourceTexts.Length == 0 + ? ImmutableArray.Empty + : ImmutableArray.Create(new NoneAnalyzerReference(readGeneratedFiles, generatedSourceTexts)); } protected override void DisposeCore() { // Do nothing } - - private static ImmutableArray CreateAnalyzerReferences(bool readGeneratedFiles, ImmutableArray<(SourceText SourceText, string Path)> generatedSourceTexts) => - readGeneratedFiles && generatedSourceTexts.Length == 0 - ? ImmutableArray.Empty - : ImmutableArray.Create(new NoneAnalyzerReference(readGeneratedFiles, generatedSourceTexts)); } /// diff --git a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs index 061a08c..4e29328 100644 --- a/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs +++ b/src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs @@ -21,42 +21,31 @@ namespace Basic.CompilerLog.Util.Impl; internal sealed class BasicAnalyzerHostOnDisk : BasicAnalyzerHost { internal OnDiskLoader Loader { get; } - internal new ImmutableArray AnalyzerReferences { get; } + protected override ImmutableArray AnalyzerReferencesCore { get; } internal string AnalyzerDirectory => Loader.AnalyzerDirectory; - private BasicAnalyzerHostOnDisk( - OnDiskLoader loader, - ImmutableArray analyzerReferences) - : base(BasicAnalyzerKind.OnDisk, ImmutableArray.CastUp(analyzerReferences)) - { - Loader = loader; - AnalyzerReferences = analyzerReferences; - } - - internal static BasicAnalyzerHostOnDisk Create( - CompilerLogReader reader, - List analyzers, - BasicAnalyzerHostOptions options) + internal BasicAnalyzerHostOnDisk(CompilerLogReader reader, List analyzers, BasicAnalyzerHostOptions options) + : base(BasicAnalyzerKind.OnDisk, options) { var name = $"{nameof(BasicAnalyzerHostOnDisk)} {Guid.NewGuid():N}"; - var loader = new OnDiskLoader(name, options); - Directory.CreateDirectory(loader.AnalyzerDirectory); + Loader = new OnDiskLoader(name, options); + Directory.CreateDirectory(Loader.AnalyzerDirectory); // Now create the AnalyzerFileReference. This won't actually pull on any assembly loading // until later so it can be done at the same time we're building up the files. - var builder = ImmutableArray.CreateBuilder(analyzers.Count); + var builder = ImmutableArray.CreateBuilder(analyzers.Count); foreach (var data in analyzers) { - var path = Path.Combine(loader.AnalyzerDirectory, data.FileName); + var path = Path.Combine(Loader.AnalyzerDirectory, data.FileName); using var fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None); reader.CopyAssemblyBytes(data.Mvid, fileStream); fileStream.Dispose(); - builder.Add(new AnalyzerFileReference(path, loader)); + builder.Add(new AnalyzerFileReference(path, Loader)); } - return new BasicAnalyzerHostOnDisk(loader, builder.MoveToImmutable()); + AnalyzerReferencesCore = builder.MoveToImmutable(); } protected override void DisposeCore()