File: FileUtilities.cs
Web Access
Project: src\src\Razor\src\Shared\Microsoft.AspNetCore.Razor.Utilities.Shared\Microsoft.AspNetCore.Razor.Utilities.Shared.csproj (Microsoft.AspNetCore.Razor.Utilities.Shared)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Globalization;
using System.IO;
 
namespace Microsoft.AspNetCore.Razor.Utilities;
 
internal static class FileUtilities
{
    private const string RazorExtension = ".razor";
    private const string CSHtmlExtension = ".cshtml";
 
    public static bool IsAnyRazorFilePath(ReadOnlySpan<char> filePath, StringComparison comparison)
    {
        return IsRazorComponentFilePath(filePath, comparison) ||
               IsMvcFilePath(filePath, comparison);
    }
 
    public static bool IsRazorComponentFilePath(ReadOnlySpan<char> filePath, StringComparison comparison)
    {
        return AdjustToUsableFilePath(filePath).EndsWith(RazorExtension, comparison);
    }
 
    public static bool IsMvcFilePath(ReadOnlySpan<char> filePath, StringComparison comparison)
    {
        return AdjustToUsableFilePath(filePath).EndsWith(CSHtmlExtension, comparison);
    }
 
    private static ReadOnlySpan<char> AdjustToUsableFilePath(ReadOnlySpan<char> filePath)
    {
        // In VS Code we sometimes get odd uris with query string components as the file path, for example on the left side of
        // a diff view. In those cases Roslyn will create a document and the file path will be set to the full raw Uri send via
        // VS Code. When trying to find out the file extension for those Uris, we need to strip off the query string.
        //
        // For example we get:
        //   git:/c:/Users/dawengie/source/repos/razor01/Pages/Index.cshtml?%7B%22path%22:%22c:%5C%5CUsers%5C%5Cdawengie%5C%5Csource%5C%5Crepos%5C%5Crazor01%5C%5CPages%5C%5CIndex.cshtml%22,%22ref%22:%22~%22%7D
        //
        // Given colons and question marks are unlikely, or illegal, file path characters the risk of false positives here is hopefully low.
        // Normalized paths from the LSP editor project system could trigger this, because "C:\Goo" would become "C:/Goo", but thats not
        // what we're looking for, and it would be surprising for those paths to have a question mark in them after the drive letter.
        if (filePath.IndexOf(":/") is int colonSlash and > 0 &&
           filePath.IndexOf('?') is int realPathEnd &&
           realPathEnd > colonSlash)
        {
            return filePath[..realPathEnd];
        }
 
        return filePath;
    }
 
    /// <summary>
    /// Generate a file path adjacent to the input path that has the
    /// specified file extension, using numbers to differentiate for
    /// any collisions.
    /// </summary>
    /// <param name="path">The input file path.</param>
    /// <param name="extension">The input file extension with a prepended ".".</param>
    /// <returns>A non-existent file path with a name in the specified format and a corresponding extension.</returns>
    public static string GenerateUniquePath(string path, string extension)
    {
        var directoryName = Path.GetDirectoryName(path).AssumeNotNull();
        var baseFileName = Path.GetFileNameWithoutExtension(path);
 
        var n = 0;
        string uniquePath;
        do
        {
            var identifier = n > 0 ? n.ToString(CultureInfo.InvariantCulture) : string.Empty;  // Make it look nice
 
            uniquePath = Path.Combine(directoryName, $"{baseFileName}{identifier}{extension}");
            n++;
        }
        while (File.Exists(uniquePath));
 
        return uniquePath;
    }
}