Skip to content

Commit

Permalink
Collapse separators with forward slash
Browse files Browse the repository at this point in the history
  • Loading branch information
jjonescz committed Apr 22, 2024
1 parent 3027ed4 commit 7ba5450
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 1 deletion.
57 changes: 57 additions & 0 deletions src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,63 @@ class C
Assert.Null(cmd.AnalyzerOptions);
}

[Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72657")]
public void AnalyzerConfig_DoubleSlash(bool doubleSlash1, bool doubleSlash2)
{
var dir = Temp.CreateDirectory();
var analyzer = new CompilationAnalyzerWithSeverity(DiagnosticSeverity.Warning, configurable: true);
var src = dir.CreateFile("Class1.cs").WriteAllText("""
public class C
{
public void M() { }
}
""");

// The analyzer should produce a warning.
var output = VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, analyzers: [analyzer], expectedWarningCount: 1);
AssertEx.Equal("Class1.cs(1,1): warning ID1000:", output.Trim());

// But not when this editorconfig is applied.
var editorconfig = dir.CreateFile(".editorconfig").WriteAllText("""
root = true

[*.cs]
dotnet_analyzer_diagnostic.severity = none

generated_code = true
""");
var cmd = CreateCSharpCompiler(
[
"/nologo",
"/preferreduilang:en",
"/t:library",
"/analyzerconfig:" + modifyPath(editorconfig.Path, doubleSlash1),
modifyPath(src.Path, doubleSlash2),
],
[analyzer]);
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var exitCode = cmd.Run(outWriter);
Assert.Equal(0, exitCode);
AssertEx.Equal("", outWriter.ToString());

static string modifyPath(string path, bool doubleSlash)
{
if (!doubleSlash)
{
return path;
}

// Find the second-to-last slash.
char[] separators = ['/', '\\'];
var lastSlashIndex = path.LastIndexOfAny(separators);
lastSlashIndex = path.LastIndexOfAny(separators, lastSlashIndex - 1);

// Duplicate that slash.
var lastSlash = path[lastSlashIndex];
return path[0..lastSlashIndex] + lastSlash + path[lastSlashIndex..];
}
}

[Fact]
public void AnalyzerConfigWithOptions()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,33 @@ public void EditorConfigToDiagnostics()
}, options.Select(o => o.TreeOptions).ToArray());
}

[Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72657")]
[InlineData("/", "/")]
[InlineData("/a/b/c/", "/a/b/c/")]
[InlineData("/a/b//c/", "/a/b/c/")]
[InlineData("/a/b/c/", "/a/b//c/")]
[InlineData("/a/b//c/", "/a/b//c/")]
[InlineData("/a/b/c//", "/a/b/c/")]
[InlineData("/a/b/c/", "/a/b/c//")]
[InlineData("/a/b/c//", "/a/b/c//")]
[InlineData("/a/b//c/", "/a/b///c/")]
public void EditorConfigToDiagnostics_DoubleSlash(string prefix1, string prefix2)
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse("""
[*.cs]
dotnet_diagnostic.cs000.severity = none
""",
prefix1 + ".editorconfig"));

var options = GetAnalyzerConfigOptions([prefix2 + "test.cs"], configs);
configs.Free();

Assert.Equal([
CreateImmutableDictionary(("cs000", ReportDiagnostic.Suppress))
], options.Select(o => o.TreeOptions).ToArray());
}

[Fact]
public void LaterSectionOverrides()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,5 +420,17 @@ public void GetRelativePath_EnsureNo_IndexOutOfRangeException_Unix()
var result = PathUtilities.GetRelativePath(@"/A/B/", @"/A/B");
Assert.Equal(expected, result);
}

[Theory]
[InlineData(@"//a/b/c", @"//a/b/c")]
[InlineData(@"/a\b/c/", @"/a/b/c/")]
[InlineData(@"\a\b/c/", @"/a/b/c/")]
[InlineData(@"C:\\a", @"C:/a")]
[InlineData(@"C:\a\b\c\", @"C:/a/b/c/")]
[InlineData(@"/\a", @"//a")]
public void CollapseWithForwardSlash(string input, string output)
{
AssertEx.Equal(output, PathUtilities.CollapseWithForwardSlash(input));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath)

var sectionKey = _sectionKeyPool.Allocate();

var normalizedPath = PathUtilities.NormalizeWithForwardSlash(sourcePath);
var normalizedPath = PathUtilities.CollapseWithForwardSlash(sourcePath);
normalizedPath = PathUtilities.ExpandAbsolutePathWithRelativeParts(normalizedPath);

// If we have a global config, add any sections that match the full path. We can have at most one section since
Expand Down
37 changes: 37 additions & 0 deletions src/Compilers/Core/Portable/FileSystem/PathUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.PooledObjects;

Expand Down Expand Up @@ -780,6 +781,42 @@ public static bool IsValidFilePath([NotNullWhen(true)] string? fullPath)
public static string NormalizeWithForwardSlash(string p)
=> DirectorySeparatorChar == '/' ? p : p.Replace(DirectorySeparatorChar, '/');

/// <summary>
/// Replaces all sequences of '\' or '/' with a single '/' but preserves UNC prefix '//'.
/// </summary>
public static string CollapseWithForwardSlash(string p)
{
var sb = new StringBuilder(p.Length);

int start = 0;
if (p.Length > 1 && IsAnyDirectorySeparator(p[0]) && IsAnyDirectorySeparator(p[1]))
{
// Preserve UNC paths.
sb.Append("//");
start = 2;
}

bool wasDirectorySeparator = false;
for (int i = start; i < p.Length; i++)
{
if (IsAnyDirectorySeparator(p[i]))
{
if (!wasDirectorySeparator)
{
sb.Append('/');
}
wasDirectorySeparator = true;
}
else
{
sb.Append(p[i]);
wasDirectorySeparator = false;
}
}

return sb.ToString();
}

/// <summary>
/// Takes an absolute path and attempts to expand any '..' or '.' into their equivalent representation.
/// </summary>
Expand Down

0 comments on commit 7ba5450

Please sign in to comment.