// 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); } } }