Skip to content

Commit

Permalink
Map global editor config sections (#111)
Browse files Browse the repository at this point in the history
* Detect global editor config from SourceText

* Ability to rewrite sections in a global config

* Mapping the paths during export

* Fix

* Coverage

* Coverage
  • Loading branch information
jaredpar authored Jan 30, 2024
1 parent d071044 commit aa63ad6
Show file tree
Hide file tree
Showing 9 changed files with 433 additions and 5 deletions.
3 changes: 2 additions & 1 deletion src/Basic.CompilerLog.UnitTests/CompilerLogFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ End Module
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="resource.txt" />
<AdditionalFiles Include="additional.txt" />
<AdditionalFiles Include="additional.txt" FixtureKey="true" />
<CompilerVisibleItemMetadata Include="AdditionalFiles" MetadataName="FixtureKey" />
</ItemGroup>
</Project>
""", TestBase.DefaultEncoding);
Expand Down
12 changes: 12 additions & 0 deletions src/Basic.CompilerLog.UnitTests/CompilerLogReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ public void CreateInvalidZipFile()
Assert.Throws<CompilerLogException>(() => CompilerLogReader.Create(stream, leaveOpen: true, BasicAnalyzerHostOptions.None));
}

[Fact]
public void GlobalConfigPathMap()
{
using var reader = CompilerLogReader.Create(Fixture.ConsoleComplexComplogPath.Value);
var data = reader.ReadCompilationData(0);
var provider = (BasicAnalyzerConfigOptionsProvider)data.AnalyzerConfigOptionsProvider;
var additonalText = data.AdditionalTexts.Single(x => x.Path.Contains("additional.txt"));
var options = provider.GetOptions(additonalText);
Assert.True(options.TryGetValue("build_metadata.AdditionalFiles.FixtureKey", out var value));
Assert.Equal("true", value);
}

[Fact]
public void MetadataVersion()
{
Expand Down
27 changes: 27 additions & 0 deletions src/Basic.CompilerLog.UnitTests/ExportUtilTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,33 @@ public void GeneratedTextExcludeAnalyzers()
}, runBuild: false);
}

/// <summary>
/// Make sure that global configs get their full paths mapped to the new location on disk
/// </summary>
[Fact]
public void GlobalConfigMapsPaths()
{
TestExport(Fixture.ConsoleComplexComplogPath.Value, expectedCount: 1, verifyExportCallback: void (string path) =>
{
var configFilePath = Directory
.EnumerateFiles(path, "console-complex.GeneratedMSBuildEditorConfig.editorconfig", SearchOption.AllDirectories)
.Single();
var found = false;
var pattern = $"[{path}";
foreach (var line in File.ReadAllLines(configFilePath))
{
if (line.StartsWith(pattern, StringComparison.Ordinal))
{
found = true;
break;
}
}
Assert.True(found);
}, runBuild: true);
}

[Fact]
public void ConsoleMultiTarget()
{
Expand Down
157 changes: 157 additions & 0 deletions src/Basic.CompilerLog.UnitTests/RoslynUtilTests.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,127 @@
using System.Drawing.Drawing2D;
using Basic.CompilerLog.Util;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.VisualBasic;
using Xunit;

namespace Basic.CompilerLog.UnitTests;

public sealed class RoslynUtilTests
{
public readonly struct IsGlobalConfigData(bool expected, string contents, int id)
{
public bool IsExpect { get; } = expected;
public string Contents { get; } = contents;
public override string ToString() => $"{nameof(IsGlobalConfigData)}-{id}";
}

public static IEnumerable<object[]> GetIsGlobalConfigData()
{
return GetRaw().Select(x => new object[] { x });

static IEnumerable<IsGlobalConfigData> GetRaw()
{
var index = 0;
yield return new IsGlobalConfigData(
false,
"",
index++);

yield return new IsGlobalConfigData(
false,
"""
is_global = true
""",
index++);

yield return new IsGlobalConfigData(
true,
"""
is_global = true
[examlpe]
""",
index++);

yield return new IsGlobalConfigData(
false,
"""
is_global = false
""",
index++);

yield return new IsGlobalConfigData(
true,
"""
is_global = false
is_global = true
[example]
""",
index++);

// Don't read past the first section
yield return new IsGlobalConfigData(
false,
"""
[c:\example.cs]
is_global = false
is_global = true
""",
index++);

// ignore comments
yield return new IsGlobalConfigData(
false,
"""
;is_global = true
a = 3
[section]
""",
index++);

// Super long lines
yield return new IsGlobalConfigData(
false,
$"""
;{new string('a', 1000)}
;is_global = true
a = 3
""",
index++);

// Super long lines
yield return new IsGlobalConfigData(
true,
$"""
;{new string('a', 1000)}
is_global = true
a = 3
[section]
""",
index++);

// Every new line combination
string[] newLines =
[
"\r\n",
"\n",
"\r",
"\u2028",
"\u2029",
$"{(char)0x85}"
];

foreach (var newLine in newLines)
{
var content = $"is_global = true{newLine}[section]";
yield return new IsGlobalConfigData(
true,
content,
index++);
}
}
}

[Fact]
public void ParseAllVisualBasicEmpty()
{
Expand All @@ -20,4 +135,46 @@ public void ParseAllCSharpEmpty()
var result = RoslynUtil.ParseAllCSharp([], CSharpParseOptions.Default);
Assert.Empty(result);
}

[Theory]
[MemberData(nameof(GetIsGlobalConfigData))]
public void IsGlobalConfig(IsGlobalConfigData data)
{
var sourceText = SourceText.From(data.Contents);
var actual = RoslynUtil.IsGlobalEditorConfigWithSection(sourceText);
Assert.Equal(data.IsExpect, actual);
}

[Fact]
public void RewriteGlobalEditorConfigPaths()
{
Core(
"""
is_global = true
[c:\example.cs]
""",
"""
is_global = true
[d:\example.cs]
""",
x => x.Replace("c:", "d:"));

Core(
"""
is_global = true
[c:\example.cs]
""",
"""
is_global = true
[c:\test.cs]
""",
x => x.Replace("example", "test"));

void Core(string content, string expected, Func<string, string> mapFunc)
{
var sourceText = SourceText.From(content);
var actual = RoslynUtil.RewriteGlobalEditorConfigSections(sourceText, mapFunc);
Assert.Equal(expected, actual);
}
}
}
12 changes: 11 additions & 1 deletion src/Basic.CompilerLog.Util/CompilerLogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using MessagePack;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Text;
Expand Down Expand Up @@ -355,7 +356,16 @@ void HandleCryptoKeyFile(string contentHash)
var list = new List<AnalyzerConfig>();
foreach (var tuple in analyzerConfigList)
{
list.Add(AnalyzerConfig.Parse(tuple.SourceText, tuple.Path));
var configText = tuple.SourceText;
if (RoslynUtil.IsGlobalEditorConfigWithSection(configText))
{
var configTextContent = RoslynUtil.RewriteGlobalEditorConfigSections(
configText,
path => NormalizePath(path));
configText = SourceText.From(configTextContent, configText.Encoding);
}

list.Add(AnalyzerConfig.Parse(configText, tuple.Path));
}

analyzerConfigSet = AnalyzerConfigSet.Create(list);
Expand Down
28 changes: 25 additions & 3 deletions src/Basic.CompilerLog.Util/ExportUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal ContentBuilder(string destinationDirectory, string originalProjectFileP
Directory.CreateDirectory(EmbeddedResourceDirectory);
}

private string GetNewSourcePath(string originalFilePath)
internal string GetNewSourcePath(string originalFilePath)
{
string filePath;
if (originalFilePath.StartsWith(OriginalProjectDirectory, PathUtil.Comparison))
Expand All @@ -61,6 +61,13 @@ private string GetNewSourcePath(string originalFilePath)
return filePath;
}

internal string WriteContent(string originalFilePath, string contents)
{
var newFilePath = GetNewSourcePath(originalFilePath);
File.WriteAllText(newFilePath, contents);
return newFilePath;
}

/// <summary>
/// Writes the content to the new directory structure and returns the full path of the
/// file that was written.
Expand Down Expand Up @@ -299,8 +306,23 @@ void WriteContent()
continue;
}

using var contentStream = Reader.GetContentStream(tuple.ContentHash);
var filePath = builder.WriteContent(tuple.FilePath, contentStream);
string? filePath = null;
if (tuple.Kind == RawContentKind.AnalyzerConfig)
{
var sourceText = Reader.GetSourceText(tuple.ContentHash, data.ChecksumAlgorithm);
if (RoslynUtil.IsGlobalEditorConfigWithSection(sourceText))
{
var content = RoslynUtil.RewriteGlobalEditorConfigSections(sourceText, x => builder.GetNewSourcePath(x));
filePath = builder.WriteContent(tuple.FilePath, content);
}
}

if (filePath is null)
{
using var contentStream = Reader.GetContentStream(tuple.ContentHash);
filePath = builder.WriteContent(tuple.FilePath, contentStream);
}

commandLineList.Add($@"{prefix}{FormatPathArgument(filePath)}");
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Basic.CompilerLog.Util/Extensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down
8 changes: 8 additions & 0 deletions src/Basic.CompilerLog.Util/Polyfill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ internal static string[] Split(this string @this, char separator, StringSplitOpt
internal static string[] Split(this string @this, char separator, int count, StringSplitOptions options = StringSplitOptions.None) =>
@this.Split(new char[] { separator }, count, options);

internal static void Append(this StringBuilder @this, ReadOnlySpan<char> value)
{
foreach (var c in value)
{
@this.Append(c);
}
}

internal static bool StartsWith(this ReadOnlySpan<char> @this, string value, StringComparison comparisonType) =>
@this.StartsWith(value.AsSpan(), comparisonType);

Expand Down
Loading

0 comments on commit aa63ad6

Please sign in to comment.