Skip to content

Commit

Permalink
Fixes a few issues (#129)
Browse files Browse the repository at this point in the history
* Fixes a few issues

1. The None host uses an in memory source generator
2. Fixed a few places paths were not normalized

closes #126
closes #127

* fix

* more

* test gap

* more
  • Loading branch information
jaredpar committed May 7, 2024
1 parent 2fbe248 commit 926eb22
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 65 deletions.
10 changes: 5 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
<PackageVersion Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.13.5" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.0" />
<PackageVersion Include="MessagePack" Version="2.5.129" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.Features" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.9.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.VisualBasic.Features" Version="4.9.2" />
<PackageVersion Include="Microsoft.Extensions.ObjectPool" Version="7.0.13" />
<PackageVersion Include="Microsoft.NET.Test.SDK" Version="17.1.0" />
<PackageVersion Include="Mono.Options" Version="6.12.0.148" />
Expand Down
37 changes: 37 additions & 0 deletions src/Basic.CompilerLog.UnitTests/BasicAnalyzerHostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
using System.Text;
using System.Threading.Tasks;
using Xunit;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.CSharp;



#if NETCOREAPP
using System.Runtime.Loader;
Expand Down Expand Up @@ -50,6 +54,39 @@ public void NoneProps()
Assert.Empty(host.GeneratedSourceTexts);
}

/// <summary>
/// What happens when two separate generators produce files with the same name?
/// </summary>
[Fact]
public void NoneConflictingFileNames()
{
var root = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? @"c:\code"
: "/code";
var sourceText1 = SourceText.From("// file 1", CommonUtil.ContentEncoding);
var sourceText2 = SourceText.From("// file 2", CommonUtil.ContentEncoding);
ImmutableArray<(SourceText SourceText, string FilePath)> generatedTexts =
[
(sourceText1, Path.Combine(root, "file.cs")),
(sourceText2, Path.Combine(root, "file.cs")),
];
var host = new BasicAnalyzerHostNone(generatedTexts);
var compilation = CSharpCompilation.Create(
"example",
[],
Basic.Reference.Assemblies.Net60.References.All);
var driver = CSharpGeneratorDriver.Create([host.Generator]);
driver.RunGeneratorsAndUpdateCompilation(compilation, out var compilation2, out var diagnostics);
Assert.Empty(diagnostics);
var syntaxTrees = compilation2.SyntaxTrees.ToList();
Assert.Equal(2, syntaxTrees.Count);
Assert.Equal("// file 1", syntaxTrees[0].ToString());
Assert.EndsWith("file.cs", syntaxTrees[0].FilePath);
Assert.Equal("// file 2", syntaxTrees[1].ToString());
Assert.EndsWith("file.cs", syntaxTrees[1].FilePath);
Assert.NotEqual(syntaxTrees[0].FilePath, syntaxTrees[1].FilePath);
}

[Fact]
public void Error()
{
Expand Down
10 changes: 5 additions & 5 deletions src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public void AnalyzerLoadCaching(BasicAnalyzerKind kind)
}

using var reader = CompilerLogReader.Create(Fixture.Console.Value.CompilerLogPath, kind);
var data = reader.ReadRawCompilationData(0).Item2;
var (compilerCall, data) = reader.ReadRawCompilationData(0);

var host1 = reader.ReadAnalyzers(data);
var host2 = reader.ReadAnalyzers(data);
Expand Down Expand Up @@ -311,14 +311,14 @@ public void NoneHostGeneratedFilesShouldBeLast()
}

[Fact]
public void NoneHostHasNoGenerators()
public void NoneHostHasSingelGenerator()
{
using var reader = CompilerLogReader.Create(Fixture.Console.Value.CompilerLogPath, BasicAnalyzerKind.None);
var data = reader.ReadCompilationData(0);
var compilation1 = data.Compilation;
var compilation2 = data.GetCompilationAfterGenerators();
Assert.Same(compilation1, compilation2);
Assert.Empty(data.AnalyzerReferences);
Assert.NotSame(compilation1, compilation2);
Assert.Single(data.AnalyzerReferences);
}

[Fact]
Expand All @@ -329,7 +329,7 @@ public void NoneHostAddsNoGeneratorIfNoGeneratedSource()
var compilation1 = data.Compilation;
var compilation2 = data.GetCompilationAfterGenerators();
Assert.Same(compilation1, compilation2);
Assert.Empty(data.AnalyzerReferences);
Assert.Single(data.AnalyzerReferences);
}

[Fact]
Expand Down
22 changes: 3 additions & 19 deletions src/Basic.CompilerLog.UnitTests/SolutionReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ public SolutionReaderTests(ITestOutputHelper testOutputHelper, CompilerLogFixtur
Fixture = fixture;
}

[Fact]
public async Task DocumentsGeneratedDefaultHost()
[Theory]
[CombinatorialData]
public async Task DocumentsGeneratedDefaultHost(BasicAnalyzerKind basicAnalyzerKind)
{
var basicAnalyzerKind = BasicAnalyzerHost.DefaultKind;
using var reader = SolutionReader.Create(Fixture.Console.Value.CompilerLogPath, basicAnalyzerKind);
var workspace = new AdhocWorkspace();
var solution = workspace.AddSolution(reader.ReadSolutionInfo());
Expand All @@ -38,22 +38,6 @@ public async Task DocumentsGeneratedDefaultHost()
Assert.NotNull(generatedDocs.First(x => x.Name == "RegexGenerator.g.cs"));
}

[Fact]
public async Task DocumentsGeneratedNoneHost()
{
var basicAnalyzerKind = BasicAnalyzerKind.None;
using var reader = SolutionReader.Create(Fixture.Console.Value.CompilerLogPath, basicAnalyzerKind);
var workspace = new AdhocWorkspace();
var solution = workspace.AddSolution(reader.ReadSolutionInfo());
var project = solution.Projects.Single();
Assert.Empty(project.AnalyzerReferences);
var docs = project.Documents.ToList();
var generatedDocs = (await project.GetSourceGeneratedDocumentsAsync()).ToList();
Assert.Equal(5, docs.Count);
Assert.Equal("RegexGenerator.g.cs", docs.Last().Name);
Assert.Empty(generatedDocs);
}

[Fact]
public void CreateRespectLeaveOpen()
{
Expand Down
29 changes: 29 additions & 0 deletions src/Basic.CompilerLog.UnitTests/UsingAllCompilerLogTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,35 @@ public async Task EmitToDisk()
await Task.WhenAll(list);
}

/// <summary>
/// Make sure paths for generated files don't have illegal characters when using the none host
/// </summary>
/// <returns></returns>
[Fact]
public async Task GeneratedFilePathsNoneHost()
{
char[] illegalChars = ['<', '>'];
var count = 0;
await foreach (var logPath in Fixture.GetAllLogs(TestOutputHelper))
{
count++;
TestOutputHelper.WriteLine(logPath);
using var reader = CompilerCallReaderUtil.Create(logPath, BasicAnalyzerKind.None);
foreach (var data in reader.ReadAllCompilationData())
{
TestOutputHelper.WriteLine($"\t{data.CompilerCall.ProjectFileName} ({data.CompilerCall.TargetFramework})");
var generatedTrees = data.GetGeneratedSyntaxTrees();
foreach (var tree in generatedTrees)
{
foreach (var c in illegalChars)
{
Assert.DoesNotContain(c, tree.FilePath);
}
}
}
}
}

[Theory]
[CombinatorialData]
public async Task EmitToMemory(BasicAnalyzerKind basicAnalyzerKind)
Expand Down
6 changes: 0 additions & 6 deletions src/Basic.CompilerLog.Util/BinaryLogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,6 @@ public CompilationData ReadCompilationData(CompilerCall compilerCall)
var emitData = GetEmitData();
var basicAnalyzerHost = CreateAnalyzerHost();

if (basicAnalyzerHost is BasicAnalyzerHostNone none)
{
// Generated source code should appear last to match the compiler behavior.
sourceTexts.AddRange(none.GeneratedSourceTexts);
}

return compilerCall.IsCSharp ? GetCSharp() : GetVisualBasic();

CSharpCompilationData GetCSharp()
Expand Down
6 changes: 4 additions & 2 deletions src/Basic.CompilerLog.Util/CompilerCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ public sealed class CompilerCall
public string? TargetFramework { get; }
public bool IsCSharp { get; }
internal object? OwnerState { get; }
public string ProjectFileName { get; }
public string ProjectDirectory { get; }

public bool IsVisualBasic => !IsCSharp;
public string ProjectFileName => Path.GetFileName(ProjectFilePath);
public string ProjectDirectory => Path.GetDirectoryName(ProjectFilePath)!;

internal CompilerCall(
string? compilerFilePath,
Expand All @@ -70,6 +70,8 @@ internal CompilerCall(
IsCSharp = isCSharp;
OwnerState = ownerState;
_lazyArguments = arguments;
ProjectFileName = Path.GetFileName(ProjectFilePath);
ProjectDirectory = Path.GetDirectoryName(ProjectFilePath)!;
}

public string GetDiagnosticName()
Expand Down
15 changes: 4 additions & 11 deletions src/Basic.CompilerLog.Util/CompilerLogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private RawCompilationData ReadRawCompilationDataCore(int index, CompilationInfo

var references = dataPack
.References
.Select(x => new RawReferenceData(x.Mvid, x.Aliases, x.EmbedInteropTypes, x.FilePath))
.Select(x => new RawReferenceData(x.Mvid, x.Aliases, x.EmbedInteropTypes, NormalizePath(x.FilePath)))
.ToList();
var analyzers = dataPack
.Analyzers
Expand Down Expand Up @@ -300,7 +300,6 @@ public CompilationData ReadCompilationData(CompilerCall compilerCall)

var hashAlgorithm = rawCompilationData.ChecksumAlgorithm;
var sourceTexts = new List<(SourceText SourceText, string Path)>();
var generatedTexts = new List<(SourceText SourceText, string Path)>();
var analyzerConfigs = new List<(SourceText SourceText, string Path)>();
var additionalTexts = ImmutableArray.CreateBuilder<AdditionalText>();

Expand All @@ -319,10 +318,7 @@ public CompilationData ReadCompilationData(CompilerCall compilerCall)
sourceTexts.Add((GetSourceText(rawContent.ContentHash, hashAlgorithm), rawContent.FilePath));
break;
case RawContentKind.GeneratedText:
if (BasicAnalyzerKind == BasicAnalyzerKind.None)
{
generatedTexts.Add((GetSourceText(rawContent.ContentHash, hashAlgorithm), rawContent.FilePath));
}
// Nothing to do here as these are handled by the generation process.
break;
case RawContentKind.AnalyzerConfig:
analyzerConfigs.Add((GetSourceText(rawContent.ContentHash, hashAlgorithm), rawContent.FilePath));
Expand Down Expand Up @@ -370,9 +366,6 @@ public CompilationData ReadCompilationData(CompilerCall compilerCall)
}
}

// Generated source code should appear last to match the compiler behavior.
sourceTexts.AddRange(generatedTexts);

var emitData = new EmitData(
rawCompilationData.AssemblyFileName,
rawCompilationData.XmlFilePath,
Expand Down Expand Up @@ -524,9 +517,9 @@ internal BasicAnalyzerHost ReadAnalyzers(RawCompilationData rawCompilationData)
_ => throw new InvalidOperationException()
});

ImmutableArray<(SourceText SourceText, string Path)> ReadGeneratedSourceTexts()
ImmutableArray<(SourceText SourceText, string FilePath)> ReadGeneratedSourceTexts()
{
var builder = ImmutableArray.CreateBuilder<(SourceText SourceText, string Path)>();
var builder = ImmutableArray.CreateBuilder<(SourceText SourceText, string FilePath)>();
foreach (var tuple in rawCompilationData.Contents.Where(static x => x.Kind == RawContentKind.GeneratedText))
{
builder.Add((GetSourceText(tuple.ContentHash, rawCompilationData.ChecksumAlgorithm), tuple.FilePath));
Expand Down
61 changes: 54 additions & 7 deletions src/Basic.CompilerLog.Util/Impl/BasicAnalyzerHostNone.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Text;
Expand All @@ -20,26 +22,71 @@ internal sealed class BasicAnalyzerHostNone : BasicAnalyzerHost
DiagnosticSeverity.Error,
isEnabledByDefault: true);

internal ImmutableArray<(SourceText SourceText, string Path)> GeneratedSourceTexts { get; }
internal ImmutableArray<(SourceText SourceText, string FilePath)> GeneratedSourceTexts { get; }
internal BasicAnalyzerHostNoneAnalyzerReference Generator { get; }

protected override ImmutableArray<AnalyzerReference> AnalyzerReferencesCore { get; }

internal BasicAnalyzerHostNone(ImmutableArray<(SourceText SourceText, string Path)> generatedSourceTexts)
internal BasicAnalyzerHostNone(ImmutableArray<(SourceText SourceText, string FilePath)> generatedSourceTexts)
: base(BasicAnalyzerKind.None)
{
GeneratedSourceTexts = generatedSourceTexts;
AnalyzerReferencesCore = ImmutableArray<AnalyzerReference>.Empty;
Generator = new BasicAnalyzerHostNoneAnalyzerReference(this);
AnalyzerReferencesCore = [Generator];
}

internal BasicAnalyzerHostNone(string errorMessage)
: base(BasicAnalyzerKind.None)
: this(ImmutableArray<(SourceText SourceText, string FilePath)>.Empty)
{
GeneratedSourceTexts = ImmutableArray<(SourceText SourceText, string Path)>.Empty;
AnalyzerReferencesCore = ImmutableArray<AnalyzerReference>.Empty;
AddDiagnostic(Diagnostic.Create(CannotReadGeneratedFiles, Location.None, errorMessage));
}

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

/// <summary>
/// This _cannot_ be a file class. The full generated name is used in file paths of generated files. Those
/// cannot include many characters that are in the full name of a file type.
/// </summary>
/// <param name="host"></param>
internal sealed class BasicAnalyzerHostNoneAnalyzerReference(BasicAnalyzerHostNone host) : AnalyzerReference, IIncrementalGenerator
{
internal BasicAnalyzerHostNone Host { get; } = host;

public override string? FullPath => null;

[ExcludeFromCodeCoverage]
public override object Id => this;

public override ImmutableArray<DiagnosticAnalyzer> GetAnalyzers(string language) => [];

[ExcludeFromCodeCoverage]
public override ImmutableArray<DiagnosticAnalyzer> GetAnalyzersForAllLanguages() => [];

public override ImmutableArray<ISourceGenerator> GetGeneratorsForAllLanguages() => [this.AsSourceGenerator()];

public override ImmutableArray<ISourceGenerator> GetGenerators(string language) => GetGeneratorsForAllLanguages();

public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(context =>
{
var set = new HashSet<string>(PathUtil.Comparer);
foreach (var tuple in Host.GeneratedSourceTexts)
{
var fileName = Path.GetFileName(tuple.FilePath);
int count = 0;
while (!set.Add(fileName))
{
fileName = Path.Combine(count.ToString(), fileName);
count++;
}
context.AddSource(fileName, tuple.SourceText);
}
});
}
}
11 changes: 1 addition & 10 deletions src/Basic.CompilerLog.Util/SolutionReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public ProjectInfo ReadProjectInfo(int index)
var documents = new List<DocumentInfo>();
var additionalDocuments = new List<DocumentInfo>();
var analyzerConfigDocuments = new List<DocumentInfo>();
var generatedFiles = new List<DocumentInfo>();

foreach (var tuple in rawCompilationData.Contents)
{
Expand All @@ -82,12 +81,7 @@ public ProjectInfo ReadProjectInfo(int index)
Add(documents);
break;
case RawContentKind.GeneratedText:
// When the host has generators these will be added as generators are run. If the host
// doesn't have generators then we need to add them as documents now.
if (Reader.BasicAnalyzerKind == BasicAnalyzerKind.None)
{
Add(generatedFiles);
}
// These are handled by theh generators
break;
case RawContentKind.AdditionalText:
Add(additionalDocuments);
Expand Down Expand Up @@ -121,9 +115,6 @@ void Add(List<DocumentInfo> list)
}
}

// Generated files should appear last
documents.AddRange(generatedFiles);

// https://github.com/jaredpar/complog/issues/24
// Need to store project reference information at the builder point so they can be properly repacked
// here and setup in the Workspace
Expand Down
1 change: 1 addition & 0 deletions src/Scratch/CompilerBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ public void Emit()
public void LoadAnalyzers()
{
using var reader = CompilerLogReader.Create(CompilerLogPath, Kind);
var (compilerCall, data) = reader.ReadRawCompilationData(0);
var analyzers = reader.ReadAnalyzers(reader.ReadRawCompilationData(0).Item2);
foreach (var analyzer in analyzers.AnalyzerReferences)
{
Expand Down

0 comments on commit 926eb22

Please sign in to comment.