| File: Language\RazorProjectFileSystem.cs | Web Access |
| Project: src\roslyn\src\Razor\src\Compiler\Microsoft.CodeAnalysis.Razor.Compiler\src\Microsoft.CodeAnalysis.Razor.Compiler.csproj (Microsoft.CodeAnalysis.Razor.Compiler) |
// 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.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using Microsoft.AspNetCore.Razor.PooledObjects; namespace Microsoft.AspNetCore.Razor.Language; /// <summary> /// An abstraction for working with a project containing Razor files. /// </summary> public abstract partial class RazorProjectFileSystem { internal const string DefaultBasePath = "/"; public static readonly RazorProjectFileSystem Empty = new EmptyFileSystem(); /// <summary> /// Gets a sequence of <see cref="RazorProjectItem"/> under the specific path in the project. /// </summary> /// <param name="basePath">The base path.</param> /// <returns>The sequence of <see cref="RazorProjectItem"/>.</returns> /// <remarks> /// Project items returned by this method have inferred FileKinds from their corresponding file paths. /// </remarks> public abstract IEnumerable<RazorProjectItem> EnumerateItems(string basePath); /// <summary> /// Gets a <see cref="RazorProjectItem"/> for the specified path. /// </summary> /// <param name="path">The path.</param> /// <returns>The <see cref="RazorProjectItem"/>.</returns> public RazorProjectItem GetItem(string path) => GetItem(path, fileKind: null); /// <summary> /// Gets a <see cref="RazorProjectItem"/> for the specified path. /// </summary> /// <param name="path">The path.</param> /// <param name="fileKind">The file kind</param> /// <returns>The <see cref="RazorProjectItem"/>.</returns> public abstract RazorProjectItem GetItem(string path, RazorFileKind? fileKind); /// <summary> /// Gets the sequence of files named <paramref name="fileName"/> that are applicable to the specified path. /// </summary> /// <param name="path">The path of a project item.</param> /// <param name="fileName">The file name to seek.</param> /// <returns>A sequence of applicable <see cref="RazorProjectItem"/> instances.</returns> /// <remarks> /// This method returns paths starting from the project root and traverses to the directory of /// <paramref name="path"/>. /// e.g. /// /Views/Home/View.cshtml -> [ /FileName.cshtml, /Views/FileName.cshtml, /Views/Home/FileName.cshtml ] /// /// Project items returned by this method have inferred FileKinds from their corresponding file paths. /// </remarks> internal ImmutableArray<RazorProjectItem> FindHierarchicalItems(string path, string fileName) { return FindHierarchicalItems(basePath: DefaultBasePath, path, fileName); } /// <summary> /// Gets the sequence of files named <paramref name="fileName"/> that are applicable to the specified path. /// </summary> /// <param name="basePath">The base path.</param> /// <param name="path">The path of a project item.</param> /// <param name="fileName">The file name to seek.</param> /// <returns>A sequence of applicable <see cref="RazorProjectItem"/> instances.</returns> /// <remarks> /// This method returns paths starting from <paramref name="basePath"/> and traverses to the directory of /// <paramref name="path"/>. /// e.g. /// (/Views, /Views/Home/View.cshtml) -> [ /Views/FileName.cshtml, /Views/Home/FileName.cshtml ] /// /// Project items returned by this method have inferred FileKinds from their corresponding file paths. /// </remarks> internal ImmutableArray<RazorProjectItem> FindHierarchicalItems(string basePath, string path, string fileName) { ArgHelper.ThrowIfNullOrEmpty(fileName); basePath = NormalizeAndEnsureValidPath(basePath); path = NormalizeAndEnsureValidPath(path); Debug.Assert(!string.IsNullOrEmpty(path)); if (path.Length == 1) { return []; } if (!path.StartsWith(basePath, StringComparison.OrdinalIgnoreCase)) { return []; } var fileNameIndex = path.LastIndexOf('/'); if (fileNameIndex == -1) { throw new InvalidOperationException($"Cannot find file name in path '{path}'"); } var length = fileNameIndex + 1; var pathMemory = path.AsMemory(); if (pathMemory.Span[(fileNameIndex + 1)..].Equals(fileName.AsSpan(), StringComparison.Ordinal)) { pathMemory = pathMemory[..fileNameIndex]; } using var result = new PooledArrayBuilder<RazorProjectItem>(); var index = pathMemory.Length; while (index > basePath.Length && (index = pathMemory.Span.LastIndexOf('/')) >= 0) { pathMemory = pathMemory[..(index + 1)]; var itemPath = string.Create( length: pathMemory.Length + fileName.Length, state: (pathMemory, fileName), static (span, state) => { var (memory, fileName) = state; memory.Span.CopyTo(span); span = span[memory.Length..]; fileName.AsSpan().CopyTo(span); Debug.Assert(span[fileName.Length..].IsEmpty); }); var item = GetItem(itemPath, fileKind: null); result.Add(item); // Slice to exclude the trailing '/' for the next pass. pathMemory = pathMemory[..^1]; } return result.ToImmutableReversed(); } /// <summary> /// Performs validation for paths passed to methods of <see cref="RazorProjectFileSystem"/>. /// </summary> /// <param name="path">The path to validate.</param> protected virtual string NormalizeAndEnsureValidPath(string path) { ArgHelper.ThrowIfNullOrEmpty(path); if (path[0] != '/') { throw new ArgumentException(Resources.RazorProjectFileSystem_PathMustStartWithForwardSlash, nameof(path)); } return path; } /// <summary> /// Create a Razor project file system based off of a root directory. /// </summary> /// <param name="rootDirectoryPath">The directory to root the file system at.</param> /// <returns>A <see cref="RazorProjectFileSystem"/></returns> public static RazorProjectFileSystem Create(string rootDirectoryPath) { ArgHelper.ThrowIfNullOrEmpty(rootDirectoryPath); return new DefaultRazorProjectFileSystem(rootDirectoryPath); } }