Skip to content

Commit

Permalink
Surface diagnostics loading analyzers (#55)
Browse files Browse the repository at this point in the history
Co-authored-by: Jared Parsons <[email protected]>
  • Loading branch information
jaredpar and Jared Parsons committed Aug 30, 2023
1 parent 7f6412c commit 7004226
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 62 deletions.
28 changes: 19 additions & 9 deletions src/Basic.CompilerLog.Util/BasicAnalyzerHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -133,27 +134,27 @@ public enum BasicAnalyzerKind
/// </summary>
public abstract class BasicAnalyzerHost : IDisposable
{
private readonly BasicAnalyzerKind _kind;
private readonly ImmutableArray<AnalyzerReference> _analyzerReferences;
private readonly ConcurrentQueue<Diagnostic> _diagnostics = new();

public BasicAnalyzerKind Kind => _kind;
public BasicAnalyzerHostOptions Options { get; }
public BasicAnalyzerKind Kind { get; }
public ImmutableArray<AnalyzerReference> AnalyzerReferences
{
get
{
CheckDisposed();
return _analyzerReferences;
return AnalyzerReferencesCore;
}
}

protected abstract ImmutableArray<AnalyzerReference> AnalyzerReferencesCore { get; }

public bool IsDisposed { get; private set; }

protected BasicAnalyzerHost(
BasicAnalyzerKind kind,
ImmutableArray<AnalyzerReference> analyzerReferences)
protected BasicAnalyzerHost(BasicAnalyzerKind kind, BasicAnalyzerHostOptions options)
{
_kind = kind;
_analyzerReferences = analyzerReferences;
Kind = kind;
Options = options;
}

public void Dispose()
Expand Down Expand Up @@ -183,6 +184,15 @@ protected void CheckDisposed()
}
}

protected void AddDiagnostic(Diagnostic diagnostic) => _diagnostics.Enqueue(diagnostic);

/// <summary>
/// Get the current set of diagnostics. This can change as analyzers can add them during
/// execution which can happen in parallel to analysis.
/// </summary>
public List<Diagnostic> GetDiagnostics() => _diagnostics.ToList();


public static bool IsSupported(BasicAnalyzerKind kind)
{
#if NETCOREAPP
Expand Down
7 changes: 7 additions & 0 deletions src/Basic.CompilerLog.Util/CompilationData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ public Compilation GetCompilationAfterGenerators(out ImmutableArray<Diagnostic>
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;
}

Expand Down
6 changes: 3 additions & 3 deletions src/Basic.CompilerLog.Util/CompilerLogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};

Expand Down
40 changes: 22 additions & 18 deletions src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostInMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,13 @@ namespace Basic.CompilerLog.Util.Impl;
internal sealed class BasicAnalyzerHostInMemory : BasicAnalyzerHost
{
internal InMemoryLoader Loader { get; }
protected override ImmutableArray<AnalyzerReference> AnalyzerReferencesCore => Loader.AnalyzerReferences;

private BasicAnalyzerHostInMemory(
InMemoryLoader loader,
ImmutableArray<AnalyzerReference> analyzerReferences)
: base(BasicAnalyzerKind.InMemory, analyzerReferences)
{
Loader = loader;
}

internal static BasicAnalyzerHostInMemory Create(CompilerLogReader reader, List<RawAnalyzerData> analyzers, BasicAnalyzerHostOptions options)
internal BasicAnalyzerHostInMemory(CompilerLogReader reader, List<RawAnalyzerData> 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()
Expand All @@ -48,7 +41,7 @@ internal sealed class InMemoryLoader : AssemblyLoadContext
internal ImmutableArray<AnalyzerReference> AnalyzerReferences { get; }
internal AssemblyLoadContext CompilerLoadContext { get; }

internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List<RawAnalyzerData> analyzers)
internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List<RawAnalyzerData> analyzers, Action<Diagnostic> onDiagnostic)
:base(name, isCollectible: true)
{
CompilerLoadContext = options.CompilerLoadContext;
Expand All @@ -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();
Expand Down Expand Up @@ -99,7 +92,7 @@ internal sealed class InMemoryLoader
{
internal ImmutableArray<AnalyzerReference> AnalyzerReferences { get; }

internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List<RawAnalyzerData> analyzers)
internal InMemoryLoader(string name, BasicAnalyzerHostOptions options, CompilerLogReader reader, List<RawAnalyzerData> analyzers, Action<Diagnostic> onDiagnostic)
{
throw new PlatformNotSupportedException();
}
Expand Down Expand Up @@ -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<Diagnostic> 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<Diagnostic> onDiagnostic)
{
AssemblyName = assemblyName;
Loader = loader;
OnDiagnostic = onDiagnostic;
}

public override ImmutableArray<DiagnosticAnalyzer> GetAnalyzers(string language)
Expand Down Expand Up @@ -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<Type>();
}
}
Expand Down
21 changes: 9 additions & 12 deletions src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ namespace Basic.CompilerLog.Util.Impl;
/// </summary>
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",
Expand All @@ -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<AnalyzerReference> 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<AnalyzerReference>.Empty
: ImmutableArray.Create<AnalyzerReference>(new NoneAnalyzerReference(readGeneratedFiles, generatedSourceTexts));
}

protected override void DisposeCore()
{
// Do nothing
}

private static ImmutableArray<AnalyzerReference> CreateAnalyzerReferences(bool readGeneratedFiles, ImmutableArray<(SourceText SourceText, string Path)> generatedSourceTexts) =>
readGeneratedFiles && generatedSourceTexts.Length == 0
? ImmutableArray<AnalyzerReference>.Empty
: ImmutableArray.Create<AnalyzerReference>(new NoneAnalyzerReference(readGeneratedFiles, generatedSourceTexts));
}

/// <summary>
Expand Down
29 changes: 9 additions & 20 deletions src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostOnDisk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,42 +21,31 @@ namespace Basic.CompilerLog.Util.Impl;
internal sealed class BasicAnalyzerHostOnDisk : BasicAnalyzerHost
{
internal OnDiskLoader Loader { get; }
internal new ImmutableArray<AnalyzerFileReference> AnalyzerReferences { get; }
protected override ImmutableArray<AnalyzerReference> AnalyzerReferencesCore { get; }

internal string AnalyzerDirectory => Loader.AnalyzerDirectory;

private BasicAnalyzerHostOnDisk(
OnDiskLoader loader,
ImmutableArray<AnalyzerFileReference> analyzerReferences)
: base(BasicAnalyzerKind.OnDisk, ImmutableArray<AnalyzerReference>.CastUp(analyzerReferences))
{
Loader = loader;
AnalyzerReferences = analyzerReferences;
}

internal static BasicAnalyzerHostOnDisk Create(
CompilerLogReader reader,
List<RawAnalyzerData> analyzers,
BasicAnalyzerHostOptions options)
internal BasicAnalyzerHostOnDisk(CompilerLogReader reader, List<RawAnalyzerData> 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<AnalyzerFileReference>(analyzers.Count);
var builder = ImmutableArray.CreateBuilder<AnalyzerReference>(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()
Expand Down

0 comments on commit 7004226

Please sign in to comment.