diff --git a/src/SLCore.UnitTests/Common/Models/FileUriTests.cs b/src/SLCore.UnitTests/Common/Models/FileUriTests.cs index bde788b15..1c410da28 100644 --- a/src/SLCore.UnitTests/Common/Models/FileUriTests.cs +++ b/src/SLCore.UnitTests/Common/Models/FileUriTests.cs @@ -134,6 +134,47 @@ public void LocalPath_ReturnsCorrectPath() new FileUri(filePath).LocalPath.Should().Be(filePath); } + [TestMethod] + [DataRow("[", "%5B")] + [DataRow("]", "%5D")] + [DataRow("#", "%2523")] + [DataRow("@", "%40")] + [DataRow(" ", "%20")] + [DataRow("`", "%60")] + public void LocalPath_UnescapesEncodesCharacters(string reservedChar, string expectedEncoding) + { + var expectedFilePath = @$"C:\filewithRfc3986ReservedChar{reservedChar}.cs"; + var encodedFilePath = @$"file:///C:/filewithRfc3986ReservedChar{expectedEncoding}.cs"; + + new FileUri(encodedFilePath).LocalPath.Should().Be(expectedFilePath); + } + + [TestMethod] + [DataRow("[")] + [DataRow("]")] + [DataRow("@")] + [DataRow(" ")] + [DataRow("`")] + public void LocalPath_PathDoesNotHaveEncodedChars_ReturnsCorrectLocalPath(string reservedChar) + { + var notEncodedFilePath = @$"file:///C:/filewithRfc3986ReservedChar{reservedChar}.cs"; + var expectedFilePath = @$"C:\filewithRfc3986ReservedChar{reservedChar}.cs"; + + new FileUri(notEncodedFilePath).LocalPath.Should().Be(expectedFilePath); + } + + /// + /// The # character as the beginning of a fragment, so will return the path without the fragment. + /// + [TestMethod] + public void LocalPath_PathWithHashCharacter_ReturnsLocalPathWithoutHash() + { + var notEncodedFilePath = @$"file:///C:/filewithRfc3986ReservedChar#.cs"; + var expectedFilePath = @$"C:\filewithRfc3986ReservedChar"; + + new FileUri(notEncodedFilePath).LocalPath.Should().Be(expectedFilePath); + } + [TestMethod] public void SerializeDeserializeToEqualObject() { diff --git a/src/SLCore/Common/Models/FileUri.cs b/src/SLCore/Common/Models/FileUri.cs index 775de8ab4..ede896602 100644 --- a/src/SLCore/Common/Models/FileUri.cs +++ b/src/SLCore/Common/Models/FileUri.cs @@ -20,8 +20,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; +using System.Text; using SonarLint.VisualStudio.SLCore.Protocol; namespace SonarLint.VisualStudio.SLCore.Common.Models; @@ -30,7 +29,7 @@ namespace SonarLint.VisualStudio.SLCore.Common.Models; public sealed class FileUri { private readonly Uri uri; - private static readonly char[] Rfc3986ReservedCharsToEncoding = ['?', '#', '[', ']', '@']; + private static readonly char[] Rfc3986ReservedCharsToEncode = ['#', '[', ']', '@']; public FileUri(string uriString) { @@ -56,11 +55,11 @@ public override string ToString() /// private static string EscapeRfc3986ReservedCharacters(string stringToEscape) { - var charsToEscape = Rfc3986ReservedCharsToEncoding.Where(stringToEscape.Contains).ToList(); - - return !charsToEscape.Any() - ? stringToEscape - : charsToEscape.Aggregate(stringToEscape, (current, charToEscape) => current.Replace(charToEscape.ToString(), Uri.HexEscape(charToEscape))); + var stringBuilderToEscape = new StringBuilder(stringToEscape); + + return Rfc3986ReservedCharsToEncode.Aggregate(stringBuilderToEscape, + (current, charToEscape) => current.Replace(charToEscape.ToString(), Uri.HexEscape(charToEscape))) + .ToString(); } [ExcludeFromCodeCoverage]