diff --git a/Semantics.Paths/SemanticPath.cs b/Semantics.Paths/SemanticPath.cs index 3d7a6bb..b41daa7 100644 --- a/Semantics.Paths/SemanticPath.cs +++ b/Semantics.Paths/SemanticPath.cs @@ -116,9 +116,9 @@ protected override string MakeCanonical(string input) // Normalize directory separators to the current platform's preferred separator canonical = canonical.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); - // Remove trailing directory separator (except for root paths) + // Remove every trailing directory separator, so "/a//" is "/a", but stop at a root path string separator = new([Path.DirectorySeparatorChar]); - if (canonical.EndsWith(separator) && canonical.Length > separator.Length) + while (canonical.EndsWith(separator) && canonical.Length > separator.Length) { // Check if this is a Windows root path (e.g., "C:\") #if NET5_0_OR_GREATER @@ -139,14 +139,16 @@ protected override string MakeCanonical(string input) #endif // Only remove trailing separator if it's not a root path - if (!isWindowsRoot && !isUnixRoot) + if (isWindowsRoot || isUnixRoot) { + break; + } + #if NETSTANDARD2_0 - canonical = canonical.Substring(0, canonical.Length - separator.Length); + canonical = canonical.Substring(0, canonical.Length - separator.Length); #else - canonical = canonical[..^separator.Length]; + canonical = canonical[..^separator.Length]; #endif - } } return canonical; diff --git a/Semantics.Test/Paths/PathContainmentTests.cs b/Semantics.Test/Paths/PathContainmentTests.cs index 8966c89..12e3712 100644 --- a/Semantics.Test/Paths/PathContainmentTests.cs +++ b/Semantics.Test/Paths/PathContainmentTests.cs @@ -97,4 +97,29 @@ public void SiblingSharingANamePrefix_IsNotAChild() Assert.IsFalse(File(TestPaths.Absolute("home2.txt")).IsChildOf(home)); Assert.IsFalse(home.IsParentOf(Dir(TestPaths.Absolute("home2")))); } + + [TestMethod] + [DataRow(2)] + [DataRow(3)] + public void RepeatedTrailingSeparators_CanonicalizeToTheSameDirectory(int separatorCount) + { + string plain = TestPaths.Absolute("home", "user", "a"); + AbsoluteDirectoryPath doubled = Dir(plain + new string(TestPaths.Separator, separatorCount)); + AbsoluteDirectoryPath single = Dir(plain); + + Assert.AreEqual(plain, doubled.WeakString); + Assert.AreEqual(single, doubled); + Assert.IsFalse(doubled.IsChildOf(single)); + Assert.IsFalse(single.IsChildOf(doubled)); + Assert.IsFalse(single.IsParentOf(doubled)); + } + + [TestMethod] + public void RootWithRepeatedTrailingSeparators_StaysTheRoot() + { + AbsoluteDirectoryPath root = Dir(TestPaths.Root + TestPaths.Separator + TestPaths.Separator); + + Assert.AreEqual(TestPaths.Root, root.WeakString); + Assert.IsFalse(root.IsChildOf(Dir(TestPaths.Root))); + } }