// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable enable
using System.Diagnostics.CodeAnalysis;
namespace SharpGenTools.Sdk.Internal.Roslyn
{
///
/// Implements a few file name utilities that are needed by the compiler.
/// In general the compiler is not supposed to understand the format of the paths.
/// In rare cases it needs to check if a string is a valid file name or change the extension
/// (embedded resources, netmodules, output name).
/// The APIs are intentionally limited to cover just these rare cases. Do not add more APIs.
///
internal static class FileNameUtilities
{
internal const char DirectorySeparatorChar = '\\';
internal const char AltDirectorySeparatorChar = '/';
internal const char VolumeSeparatorChar = ':';
///
/// Returns true if the string represents an unqualified file name.
/// The name may contain any characters but directory and volume separators.
///
/// Path.
///
/// True if is a simple file name, false if it is null or includes a directory specification.
///
internal static bool IsFileName([NotNullWhen(returnValue: true)] string? path)
{
return IndexOfFileName(path) == 0;
}
///
/// Returns the offset in where the dot that starts an extension is, or -1 if the path doesn't have an extension.
///
///
/// Returns 0 for path ".goo".
/// Returns -1 for path "goo.".
///
private static int IndexOfExtension(string? path)
{
if (path == null)
{
return -1;
}
int length = path.Length;
int i = length;
while (--i >= 0)
{
char c = path[i];
if (c == '.')
{
if (i != length - 1)
{
return i;
}
return -1;
}
if (c == DirectorySeparatorChar || c == AltDirectorySeparatorChar || c == VolumeSeparatorChar)
{
break;
}
}
return -1;
}
///
/// Returns an extension of the specified path string.
///
///
/// The same functionality as but doesn't throw an exception
/// if there are invalid characters in the path.
///
[return: NotNullIfNotNull(parameterName: "path")]
internal static string? GetExtension(string? path)
{
if (path == null)
{
return null;
}
int index = IndexOfExtension(path);
return (index >= 0) ? path.Substring(index) : string.Empty;
}
///
/// Removes extension from path.
///
///
/// Returns "goo" for path "goo.".
/// Returns "goo.." for path "goo...".
///
[return: NotNullIfNotNull(parameterName: "path")]
private static string? RemoveExtension(string? path)
{
if (path == null)
{
return null;
}
int index = IndexOfExtension(path);
if (index >= 0)
{
return path.Substring(0, index);
}
// trim last ".", if present
if (path.Length > 0 && path[path.Length - 1] == '.')
{
return path.Substring(0, path.Length - 1);
}
return path;
}
///
/// Returns path with the extension changed to .
///
///
/// Equivalent of
///
/// If is null, returns null.
/// If path does not end with an extension, the new extension is appended to the path.
/// If extension is null, equivalent to .
///
[return: NotNullIfNotNull(parameterName: "path")]
internal static string? ChangeExtension(string? path, string? extension)
{
if (path == null)
{
return null;
}
var pathWithoutExtension = RemoveExtension(path);
if (extension == null || path.Length == 0)
{
return pathWithoutExtension;
}
if (extension.Length == 0 || extension[0] != '.')
{
return pathWithoutExtension + "." + extension;
}
return pathWithoutExtension + extension;
}
///
/// Returns the position in given path where the file name starts.
///
/// -1 if path is null.
internal static int IndexOfFileName(string? path)
{
if (path == null)
{
return -1;
}
for (int i = path.Length - 1; i >= 0; i--)
{
char ch = path[i];
if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
{
return i + 1;
}
}
return 0;
}
///
/// Get file name from path.
///
/// Unlike doesn't check for invalid path characters.
[return: NotNullIfNotNull(parameterName: "path")]
internal static string? GetFileName(string? path, bool includeExtension = true)
{
int fileNameStart = IndexOfFileName(path);
var fileName = (fileNameStart <= 0) ? path : path!.Substring(fileNameStart);
return includeExtension ? fileName : RemoveExtension(fileName);
}
}
}