diff --git a/TruePath.Tests/PathExtensionsTests.cs b/TruePath.Tests/PathExtensionsTests.cs
index 6fec2a4..b807d88 100644
--- a/TruePath.Tests/PathExtensionsTests.cs
+++ b/TruePath.Tests/PathExtensionsTests.cs
@@ -100,6 +100,28 @@ public void WithExtensionTests_Windows(string inputPath, string newExtension, st
Assert.Equal(expected, newPath.Value);
}
+ [Theory]
+ [InlineData("file.txt", "md", "file.md")]
+ [InlineData("file.txt", ".md", "file.md")]
+ [InlineData("file", "md", "file.md")]
+ [InlineData("file", ".md", "file.md")]
+ [InlineData("archive.tar.gz", "zip", "archive.tar.zip")]
+ [InlineData("archive.zip", ".tar.gz", "archive.tar.gz")]
+ [InlineData("file.txt", "", "file.")]
+ [InlineData("file.txt", null, "file")]
+ [InlineData(".gitignore", "hgignore", ".hgignore")]
+ public void WithExtensionArgumentTests(string inputPath, string? newExtension, string expected)
+ {
+ // Arrange
+ var path = new LocalPath(inputPath);
+
+ // Act
+ var newPath = path.WithExtension(newExtension);
+
+ // Assert
+ Assert.Equal(expected, newPath.Value);
+ }
+
[Theory]
[InlineData("/", "bar", "/.bar")]
[InlineData("/file", "bar", "/file.bar")]
diff --git a/TruePath/PathExtensions.cs b/TruePath/PathExtensions.cs
index 2ba4187..1131de2 100644
--- a/TruePath/PathExtensions.cs
+++ b/TruePath/PathExtensions.cs
@@ -57,11 +57,33 @@ public static string GetFilenameWithoutExtension(this IPath path) =>
///
/// The type of the path, which must implement .
/// The original path.
- /// The new extension to apply.
+ ///
+ ///
+ /// The new extension, either with or without the leading dot: both txt and .txt give the
+ /// same result, since the dot is added when missing.
+ ///
+ ///
+ /// Pass to remove the extension entirely (file.txt becomes file), or
+ /// an empty string to remove it but keep the trailing dot (file.txt becomes file.).
+ ///
+ ///
///
/// A new path of type with the modified file name component.
/// The original object is not modified.
///
+ ///
+ ///
+ /// Only the last extension is replaced: for archive.tar.gz and the extension zip, the result
+ /// is archive.tar.zip. The extension itself may contain dots, so .tar.gz applied to
+ /// archive.zip gives archive.tar.gz.
+ ///
+ /// If the file name has no extension, the new one is appended: file becomes file.txt.
+ ///
+ /// A file name consisting entirely of an extension is treated as an extension, consistently with
+ /// : .gitignore with the extension hgignore becomes
+ /// .hgignore.
+ ///
+ ///
public static TPath WithExtension(this TPath path, string? extension) where TPath : IPath =>
TPath.Create(Path.ChangeExtension(((IPath)path).Value, extension));
#else
@@ -70,11 +92,33 @@ public static TPath WithExtension(this TPath path, string? extension) whe
/// or with a new extension-like component if the original file name was empty.
///
/// The original path.
- /// The new extension to apply.
+ ///
+ ///
+ /// The new extension, either with or without the leading dot: both txt and .txt give the
+ /// same result, since the dot is added when missing.
+ ///
+ ///
+ /// Pass to remove the extension entirely (file.txt becomes file), or
+ /// an empty string to remove it but keep the trailing dot (file.txt becomes file.).
+ ///
+ ///
///
/// A new path of type with the modified file name component.
/// The original object is not modified.
///
+ ///
+ ///
+ /// Only the last extension is replaced: for archive.tar.gz and the extension zip, the result
+ /// is archive.tar.zip. The extension itself may contain dots, so .tar.gz applied to
+ /// archive.zip gives archive.tar.gz.
+ ///
+ /// If the file name has no extension, the new one is appended: file becomes file.txt.
+ ///
+ /// A file name consisting entirely of an extension is treated as an extension, consistently with
+ /// : .gitignore with the extension hgignore becomes
+ /// .hgignore.
+ ///
+ ///
public static AbsolutePath WithExtension(this AbsolutePath path, string? extension) =>
AbsolutePath.Create(Path.ChangeExtension(path.Value, extension));
@@ -83,11 +127,33 @@ public static AbsolutePath WithExtension(this AbsolutePath path, string? extensi
/// or with a new extension-like component if the original file name was empty.
///
/// The original path.
- /// The new extension to apply.
+ ///
+ ///
+ /// The new extension, either with or without the leading dot: both txt and .txt give the
+ /// same result, since the dot is added when missing.
+ ///
+ ///
+ /// Pass to remove the extension entirely (file.txt becomes file), or
+ /// an empty string to remove it but keep the trailing dot (file.txt becomes file.).
+ ///
+ ///
///
/// A new path of type with the modified file name component.
/// The original object is not modified.
///
+ ///
+ ///
+ /// Only the last extension is replaced: for archive.tar.gz and the extension zip, the result
+ /// is archive.tar.zip. The extension itself may contain dots, so .tar.gz applied to
+ /// archive.zip gives archive.tar.gz.
+ ///
+ /// If the file name has no extension, the new one is appended: file becomes file.txt.
+ ///
+ /// A file name consisting entirely of an extension is treated as an extension, consistently with
+ /// : .gitignore with the extension hgignore becomes
+ /// .hgignore.
+ ///
+ ///
public static LocalPath WithExtension(this LocalPath path, string? extension) =>
LocalPath.Create(Path.ChangeExtension(path.Value, extension));
#endif