Skip to content

Commit

Permalink
Merge pull request #72965 from CyrusNajmabadi/caseInsensitiveFilePaths
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Apr 10, 2024
2 parents 00e0017 + 16d68d3 commit 0d1e5d7
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 18 deletions.
45 changes: 30 additions & 15 deletions src/Features/LanguageServer/ProtocolUnitTests/UriTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,28 +79,43 @@ public async Task TestWorkspaceDocument_WithFileScheme(bool mutatingLspWorkspace
{
var documentFilePath = @"C:\A.cs";
var markup =
@$"<Workspace>
<Project Language=""C#"" Name=""CSProj1"" CommonReferences=""true"" FilePath=""C:\CSProj1.csproj"">
<Document FilePath=""{documentFilePath}"">
public class A
{{
}}
</Document>
</Project>
</Workspace>";
$$"""
<Workspace>
<Project Language="C#" Name="CSProj1" CommonReferences="true" FilePath="C:\CSProj1.csproj">
<Document FilePath="{{documentFilePath}}">
public class A
{
}
</Document>
</Project>
</Workspace>
""";
await using var testLspServer = await CreateXmlTestLspServerAsync(markup, mutatingLspWorkspace);

var workspaceDocument = testLspServer.TestWorkspace.CurrentSolution.Projects.Single().Documents.Single();
var expectedDocumentUri = ProtocolConversions.CreateAbsoluteUri(documentFilePath);

await testLspServer.OpenDocumentAsync(expectedDocumentUri).ConfigureAwait(false);

// Verify file is added to the misc file workspace.
var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = expectedDocumentUri }, CancellationToken.None);
Assert.False(workspace is LspMiscellaneousFilesWorkspace);
AssertEx.NotNull(document);
Assert.Equal(expectedDocumentUri, document.GetURI());
Assert.Equal(documentFilePath, document.FilePath);
// Verify file is not added to the misc file workspace.
{
var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = expectedDocumentUri }, CancellationToken.None);
Assert.False(workspace is LspMiscellaneousFilesWorkspace);
AssertEx.NotNull(document);
Assert.Equal(expectedDocumentUri, document.GetURI());
Assert.Equal(documentFilePath, document.FilePath);
}

// Try again, this time with a uri with different case sensitivity. This is supported, and is needed by Xaml.
{
var lowercaseUri = ProtocolConversions.CreateAbsoluteUri(documentFilePath.ToLowerInvariant());
Assert.NotEqual(expectedDocumentUri.AbsolutePath, lowercaseUri.AbsolutePath);
var (workspace, _, document) = await testLspServer.GetManager().GetLspDocumentInfoAsync(new LSP.TextDocumentIdentifier { Uri = lowercaseUri }, CancellationToken.None);
Assert.False(workspace is LspMiscellaneousFilesWorkspace);
AssertEx.NotNull(document);
Assert.Equal(expectedDocumentUri, document.GetURI());
Assert.Equal(documentFilePath, document.FilePath);
}
}

[Theory, CombinatorialData]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ internal readonly record struct StateChange(
/// </summary>
internal sealed partial class SolutionState
{
/// <summary>
/// Note: this insensitive comparer is busted on many systems. But we do things this way for compat with the logic
/// we've had on windows since forever.
/// </summary>
public static readonly StringComparer FilePathComparer = StringComparer.OrdinalIgnoreCase;

// the version of the workspace this solution is from
public int WorkspaceVersion { get; }
public string? WorkspaceKind { get; }
Expand All @@ -47,7 +53,7 @@ internal sealed partial class SolutionState
// holds on data calculated based on the AnalyzerReferences list
private readonly Lazy<HostDiagnosticAnalyzers> _lazyAnalyzers;

private ImmutableDictionary<string, ImmutableArray<DocumentId>> _lazyFilePathToRelatedDocumentIds = ImmutableDictionary<string, ImmutableArray<DocumentId>>.Empty;
private ImmutableDictionary<string, ImmutableArray<DocumentId>> _lazyFilePathToRelatedDocumentIds = ImmutableDictionary<string, ImmutableArray<DocumentId>>.Empty.WithComparers(FilePathComparer);

private SolutionState(
string? workspaceKind,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace Microsoft.CodeAnalysis;
internal sealed class TextDocumentStates<TState>
where TState : TextDocumentState
{
private static readonly ObjectPool<Dictionary<string, OneOrMany<DocumentId>>> s_filePathPool = new(() => new(SolutionState.FilePathComparer));

public static readonly TextDocumentStates<TState> Empty =
new([], ImmutableSortedDictionary.Create<DocumentId, TState>(DocumentIdComparer.Instance), FrozenDictionary<string, OneOrMany<DocumentId>>.Empty);

Expand Down Expand Up @@ -323,7 +325,8 @@ public void AddDocumentIdsWithFilePath(ref TemporaryArray<DocumentId> temporaryA

private FrozenDictionary<string, OneOrMany<DocumentId>> ComputeFilePathToDocumentIds()
{
using var _ = PooledDictionary<string, OneOrMany<DocumentId>>.GetInstance(out var result);
using var pooledDictionary = s_filePathPool.GetPooledObject();
var result = pooledDictionary.Object;

foreach (var (documentId, state) in _map)
{
Expand All @@ -336,6 +339,6 @@ private FrozenDictionary<string, OneOrMany<DocumentId>> ComputeFilePathToDocumen
: OneOrMany.Create(documentId);
}

return result.ToFrozenDictionary();
return result.ToFrozenDictionary(SolutionState.FilePathComparer);
}
}
17 changes: 17 additions & 0 deletions src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4845,6 +4845,23 @@ public void GetRelatedDocumentsDoesNotReturnOtherTypesOfDocuments()
Assert.Single(solution.GetRelatedDocumentIds(regularDocumentId));
}

[Theory, CombinatorialData]
public void GetRelatedDocumentsCaseInsensitive(
[CombinatorialValues("file", "File", "FILE", "FiLe")] string prefix,
[CombinatorialValues("cs", "Cs", "cS", "CS")] string extension)
{
using var workspace = CreateWorkspace();

var solution = workspace.CurrentSolution
.AddProject("TestProject1", "TestProject1", LanguageNames.CSharp)
.AddDocument("File.cs", "", filePath: "File.cs").Project.Solution
.AddProject("TestProject2", "TestProject2", LanguageNames.CSharp)
.AddDocument("file.cs", "", filePath: "file.cs").Project.Solution;

// GetDocumentIdsWithFilePath should return two, since it'll count all types of documents
Assert.Equal(2, solution.GetDocumentIdsWithFilePath($"{prefix}.{extension}").Length);
}

[Fact]
public async Task TestFrozenPartialSolution1()
{
Expand Down

0 comments on commit 0d1e5d7

Please sign in to comment.