Use normalized paths as keys in project snapshots

This commit is contained in:
David Wengier 2023-12-10 15:23:05 +11:00
Родитель 7fe9de35f2
Коммит 8cddc8b70d
3 изменённых файлов: 43 добавлений и 6 удалений

Просмотреть файл

@ -3,14 +3,27 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.Extensions.Internal;
namespace Microsoft.AspNetCore.Razor.Utilities;
internal static class FilePathNormalizer
{
private static Lazy<IEqualityComparer<string>> _lazyComparer = new Lazy<IEqualityComparer<string>>(() => new FilePathNormalizingComparer());
public static IEqualityComparer<string> Comparer => _lazyComparer.Value;
private class FilePathNormalizingComparer : IEqualityComparer<string>
{
public bool Equals(string? x, string? y) => FilePathNormalizer.FilePathsEquivalent(x, y);
public int GetHashCode([DisallowNull] string obj) => FilePathNormalizer.GetHashCode(obj);
}
public static string NormalizeDirectory(string? directoryFilePath)
{
if (directoryFilePath.IsNullOrEmpty())
@ -118,6 +131,28 @@ internal static class FilePathNormalizer
return normalizedSpan1.Equals(normalizedSpan2, FilePathComparison.Instance);
}
public static int GetHashCode(string filePath)
{
if (filePath.Length == 0)
{
return filePath.GetHashCode();
}
var filePathSpan = filePath.AsSpanOrDefault();
using var _ = ArrayPool<char>.Shared.GetPooledArray(filePathSpan.Length, out var array1);
var normalizedSpan = NormalizeCoreAndGetSpan(filePathSpan, array1);
var hashCombiner = HashCodeCombiner.Start();
foreach (var ch in normalizedSpan)
{
hashCombiner.Add(ch);
}
return hashCombiner.CombinedHash;
}
private static ReadOnlySpan<char> NormalizeCoreAndGetSpan(ReadOnlySpan<char> source, Span<char> destination)
{
var (start, length) = NormalizeCore(source, destination);

Просмотреть файл

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.AspNetCore.Razor.ProjectSystem;
using Microsoft.AspNetCore.Razor.Utilities;
using Microsoft.CodeAnalysis.CSharp;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem;
@ -23,7 +24,7 @@ internal class ProjectSnapshot : IProjectSnapshot
State = state ?? throw new ArgumentNullException(nameof(state));
_lock = new object();
_documents = new Dictionary<string, DocumentSnapshot>(FilePathComparer.Instance);
_documents = new Dictionary<string, DocumentSnapshot>(FilePathNormalizer.Comparer);
}
public ProjectKey Key => State.HostProject.Key;

Просмотреть файл

@ -11,6 +11,7 @@ using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.ProjectSystem;
using Microsoft.AspNetCore.Razor.Utilities;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text;
@ -31,8 +32,8 @@ internal class ProjectState
ProjectDifference.DocumentAdded |
ProjectDifference.DocumentRemoved;
private static readonly ImmutableDictionary<string, DocumentState> s_emptyDocuments = ImmutableDictionary.Create<string, DocumentState>(FilePathComparer.Instance);
private static readonly ImmutableDictionary<string, ImmutableArray<string>> s_emptyImportsToRelatedDocuments = ImmutableDictionary.Create<string, ImmutableArray<string>>(FilePathComparer.Instance);
private static readonly ImmutableDictionary<string, DocumentState> s_emptyDocuments = ImmutableDictionary.Create<string, DocumentState>(FilePathNormalizer.Comparer);
private static readonly ImmutableDictionary<string, ImmutableArray<string>> s_emptyImportsToRelatedDocuments = ImmutableDictionary.Create<string, ImmutableArray<string>>(FilePathNormalizer.Comparer);
private readonly object _lock;
private RazorProjectEngine _projectEngine;
@ -337,7 +338,7 @@ internal class ProjectState
return this;
}
var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithConfigurationChange(), FilePathComparer.Instance);
var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithConfigurationChange(), FilePathNormalizer.Comparer);
// If the host project has changed then we need to recompute the imports map
var importsToRelatedDocuments = s_emptyImportsToRelatedDocuments;
@ -365,7 +366,7 @@ internal class ProjectState
}
var difference = ProjectDifference.ProjectWorkspaceStateChanged;
var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithProjectWorkspaceStateChange(), FilePathComparer.Instance);
var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithProjectWorkspaceStateChange(), FilePathNormalizer.Comparer);
var state = new ProjectState(this, difference, HostProject, projectWorkspaceState, documents, ImportsToRelatedDocuments);
return state;
}
@ -435,7 +436,7 @@ internal class ProjectState
{
var itemTargetPath = importItem.FilePath.Replace('/', '\\').TrimStart('\\');
if (FilePathComparer.Instance.Equals(itemTargetPath, hostDocument.TargetPath))
if (FilePathNormalizer.Comparer.Equals(itemTargetPath, hostDocument.TargetPath))
{
// We've normalized the original importItem.FilePath into the HostDocument.TargetPath. For instance, if the HostDocument.TargetPath
// was '/_Imports.razor' it'd be normalized down into '_Imports.razor'. The purpose of this method is to get the associated document