diff --git a/README.md b/README.md index 6726a8f748261..7fa4b7a5624aa 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,12 @@ -## Welcome to the .NET Compiler Platform ("Roslyn") +

+Roslyn logo +

-[![Join the chat at https://gitter.im/dotnet/roslyn](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dotnet/roslyn?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Chat on Discord](https://discordapp.com/api/guilds/143867839282020352/widget.png)](http://aka.ms/discord-csharp-roslyn) +

The .NET Compiler Platform

-Roslyn provides open-source C# and Visual Basic compilers with rich code analysis APIs. It enables building code analysis tools with the same APIs that are used by Visual Studio. +

Join the chat at https://gitter.im/dotnet/roslyn Chat on Discord

+ +Roslyn is the open-source implementation of both the C# and Visual Basic compilers with an API surface for building code analysis tools. ### C# and Visual Basic Language Feature Suggestions @@ -11,50 +15,31 @@ If you want to suggest a new feature for the C# or Visual Basic languages go her - [dotnet/vblang](https://github.com/dotnet/vblang) for VB-specific features - [dotnet/csharplang](https://github.com/dotnet/csharplang) for features that affect both languages -## Contribute! +### Contributing -Some of the best ways to contribute are to try things out, file bugs, and join in design conversations. +All work on the C# and Visual Basic compiler happens directly on [GitHub](https://github.com/dotnet/roslyn). Both core team members and external contributors send pull requests which go through the same review process. -### Questions +If you are interested in fixing issues and contributing directly to the code base, a great way to get started is to ask some questions on [GitHub Discussions](https://github.com/dotnet/roslyn/discussions)! Then check out our [contributing guide](https://github.com/dotnet/roslyn/blob/master/docs/contributing/Building%2C%20Debugging%2C%20and%20Testing%20on%20Windows.md) which covers the following: -A great way to get started is to ask some questions! -- Start with a question on [discussions](https://github.com/dotnet/roslyn/discussions) -- You can also join in on the design discussions on [gitter](https://gitter.im/dotnet/roslyn) or [discord](http://aka.ms/discord-csharp-roslyn) +- [Coding guidelines](https://github.com/dotnet/roslyn/blob/master/docs/wiki/Contributing-Code.md) +- [The development workflow, including debugging and running tests](https://github.com/dotnet/roslyn/blob/master/docs/contributing/Building%2C%20Debugging%2C%20and%20Testing%20on%20Windows.md) +- [Submitting pull requests](https://github.com/dotnet/roslyn/blob/master/CONTRIBUTING.md) +- Finding a bug to fix in the [IDE](https://aka.ms/roslyn-ide-bugs-help-wanted) or [Compiler](https://aka.ms/roslyn-compiler-bugs-help-wanted) +- Finding a feature to implement in the [IDE](https://aka.ms/roslyn-ide-feature-help-wanted) or [Compiler](https://aka.ms/roslyn-compiler-feature-help-wanted) -### See if your issue is already being worked on! (Add your own votes using the šŸ‘ reaction) -- [IDE](https://aka.ms/roslyn-ide-in-progress) -- [Compiler](https://aka.ms/roslyn-compiler-in-progress) +### Community -### Vote in the Backlog! (Add your own votes using the šŸ‘ reaction) -- [IDE Bugs](https://aka.ms/roslyn-ide-bug-backlog) -- [IDE Features](https://aka.ms/roslyn-ide-feature-backlog) -- [Compiler Bugs](https://aka.ms/roslyn-compiler-bug-backlog) -- [Compiler Features](https://aka.ms/roslyn-compiler-features-backlog) +The Roslyn community can be found on [GitHub Discussions](https://github.com/dotnet/roslyn/discussions), where you can ask questions, voice ideas, and share your projects. -### Find a bug to fix! (Add your own votes using the šŸ‘ reaction) -- First read this guide: [How to Contribute](docs/wiki/Contributing-Code.md) -- [Building, testing and debugging the sources](docs/wiki/Building-Testing-and-Debugging.md) -- Top Bugs - - [IDE](https://aka.ms/roslyn-ide-bugs-help-wanted) - - [Compiler](https://aka.ms/roslyn-compiler-bugs-help-wanted) +To chat with other community members, you can join the Roslyn [Discord](https://discord.com/invite/tGJvv88) or [Gitter](https://gitter.im/dotnet/roslyn). -### Find a feature to implement! (Add your own votes using the šŸ‘ reaction) -- [IDE](https://aka.ms/roslyn-ide-feature-help-wanted) -- [Compiler](https://aka.ms/roslyn-compiler-feature-help-wanted) +Our [Code of Conduct](CODE-OF-CONDUCT.md) applies to all Roslyn community channels and hasĀ adoptedĀ theĀ [.NETĀ FoundationĀ CodeĀ ofĀ Conduct](https://dotnetfoundation.org/code-of-conduct). +### Documentation -### Getting started with the Roslyn APIs +Visit [Roslyn Architecture Overview](https://docs.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/compiler-api-model) to get started with Roslynā€™s APIā€™s. -If you want to get started using Roslyn's APIs to analyzer your code take a look at these links: -- [Roslyn Architecture Overview](https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/compiler-api-model) - - [Syntax APIs](https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/work-with-syntax) - - [Semantic APIs](https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/work-with-semantics) - - [Workspace APIs](https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/work-with-workspace) -- [Tutorial: Write your first analyzer and code fix](https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix) -- Useful Tools - - [Syntax Visualizer Tool](https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/syntax-visualizer) - - [Syntax Quoter Tool](http://roslynquoter.azurewebsites.net) - - Browse the source with the [enhanced source view](http://sourceroslyn.io/) +### NuGet Feeds **The latest pre-release builds** are available from the following public NuGet feeds: - [Compiler](https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet-tools): `https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json` @@ -102,8 +87,6 @@ If you want to get started using Roslyn's APIs to analyzer your code take a look [//]: # (End current test results) -This [project](CODE-OF-CONDUCT.md) has adopted the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). - ### .NET Foundation This project is part of the [.NET Foundation](http://www.dotnetfoundation.org/projects) along with other diff --git a/eng/build.ps1 b/eng/build.ps1 index c00338c30d482..37f78b3268e6f 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -417,10 +417,7 @@ function TestUsingRunTests() { $args += " --include 'Microsoft.CodeAnalysis.Workspaces.MSBuild.UnitTests'" if ($lspEditor) { - $args += " --testfilter FullyQualifiedName~Roslyn.VisualStudio.IntegrationTests.LanguageServerProtocol|Editor=LanguageServerProtocol" - } - else { - $args += " --testfilter FullyQualifiedName!~Roslyn.VisualStudio.IntegrationTests.LanguageServerProtocol" + $args += " --testfilter Editor=LanguageServerProtocol" } } diff --git a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/MatchResult.cs b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/MatchResult.cs index a6f0f0cf5d0ba..53e5159a601ea 100644 --- a/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/MatchResult.cs +++ b/src/EditorFeatures/Core/Implementation/IntelliSense/AsyncCompletion/MatchResult.cs @@ -89,14 +89,14 @@ public int CompareTo(MatchResult other, string filterText) ImmutableArray.Create>( // Prefer the item that matches a longer prefix of the filter text. (f, s) => f.RoslynCompletionItem.FilterText.GetCaseInsensitivePrefixLength(s), + // If there are "Abc" vs "abc", we should prefer the case typed by user. + (f, s) => f.RoslynCompletionItem.FilterText.GetCaseSensitivePrefixLength(s), // If the lengths are the same, prefer the one with the higher match priority. // But only if it's an item that would have been hard selected. We don't want // to aggressively select an item that was only going to be softly offered. (f, s) => f.RoslynCompletionItem.Rules.SelectionBehavior == CompletionItemSelectionBehavior.HardSelection ? f.RoslynCompletionItem.Rules.MatchPriority : MatchPriority.Default, - // If there are "Abc" vs "abc", we should prefer the case typed by user. - (f, s) => f.RoslynCompletionItem.FilterText.GetCaseSensitivePrefixLength(s), // Prefer Intellicode items. (f, s) => f.RoslynCompletionItem.IsPreferredItem()); } diff --git a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs index 4de9239ba5e21..f05a6b0d60038 100644 --- a/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs +++ b/src/EditorFeatures/Test/EditAndContinue/EditAndContinueWorkspaceServiceTests.cs @@ -1124,46 +1124,87 @@ public async Task BreakMode_RudeEdits() { var moduleId = Guid.NewGuid(); - using (var workspace = CreateWorkspace()) + using var workspace = CreateWorkspace(); + var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); + _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); + + var service = CreateEditAndContinueService(workspace); + + var debuggingSession = StartDebuggingSession(service); + + StartEditSession(service); + + // change the source (rude edit): + var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M1() { System.Console.WriteLine(1); } }")); + var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None).ConfigureAwait(false); + AssertEx.Equal(new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) }, + diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); + + // validate solution update status and emit: + Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None).ConfigureAwait(false)); + + var (updates, emitDiagnostics) = await service.EmitSolutionUpdateAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, CancellationToken.None).ConfigureAwait(false); + Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); + Assert.Empty(updates.Updates); + Assert.Empty(emitDiagnostics); + + EndEditSession(service, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); + EndDebuggingSession(service); + + AssertEx.SetEqual(new[] { moduleId }, debuggingSession.Test_GetModulesPreparedForUpdate()); + + AssertEx.Equal(new[] { - var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); - _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); + "Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0", + "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0", + "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=20|RudeEditSyntaxKind=8875|RudeEditBlocking=True" + }, _telemetryLog); + } - var service = CreateEditAndContinueService(workspace); + [Fact] + public async Task BreakMode_RudeEdits_SourceGenerators() + { + var sourceV1 = @" +// GENERATE: class G { int X1 => 1; } - var debuggingSession = StartDebuggingSession(service); +class C { int Y => 1; } +"; + var sourceV2 = @" +// GENERATE: class G { int X2 => 1; } - StartEditSession(service); +class C { int Y => 2; } +"; - // change the source (rude edit): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M1() { System.Console.WriteLine(1); } }")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource }; - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None).ConfigureAwait(false); - AssertEx.Equal(new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.method) }, - diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); + using var workspace = CreateWorkspace(); + var project = AddDefaultTestProject(workspace, sourceV1, generator: generator); - // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None).ConfigureAwait(false)); + var service = CreateEditAndContinueService(workspace); - var (updates, emitDiagnostics) = await service.EmitSolutionUpdateAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var debuggingSession = StartDebuggingSession(service); + StartEditSession(service); - EndEditSession(service, documentsWithRudeEdits: ImmutableArray.Create(document2.Id)); - EndDebuggingSession(service); + // change the source: + var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + workspace.ChangeDocument(document1.Id, SourceText.From(sourceV2, Encoding.UTF8)); - AssertEx.SetEqual(new[] { moduleId }, debuggingSession.Test_GetModulesPreparedForUpdate()); + var generatedDocument = (await workspace.CurrentSolution.Projects.Single().GetSourceGeneratedDocumentsAsync().ConfigureAwait(false)).Single(); - AssertEx.Equal(new[] - { - "Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0", - "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0", - "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=20|RudeEditSyntaxKind=8875|RudeEditBlocking=True" - }, _telemetryLog); - } + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noDocumentActiveSpans, CancellationToken.None).ConfigureAwait(false); + AssertEx.Equal(new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_will_prevent_the_debug_session_from_continuing, FeaturesResources.property_) }, + diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}")); + + var (updates, emitDiagnostics) = await service.EmitSolutionUpdateAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, CancellationToken.None).ConfigureAwait(false); + Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); + Assert.Empty(updates.Updates); + Assert.Empty(emitDiagnostics); + + EndEditSession(service, documentsWithRudeEdits: ImmutableArray.Create(generatedDocument.Id)); + EndDebuggingSession(service); } [Fact] @@ -1364,45 +1405,43 @@ public async Task BreakMode_SyntaxError() { var moduleId = Guid.NewGuid(); - using (var workspace = CreateWorkspace()) - { - var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); - _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); + using var workspace = CreateWorkspace(); + var project = AddDefaultTestProject(workspace, "class C1 { void M() { System.Console.WriteLine(1); } }"); + _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId); - var service = CreateEditAndContinueService(workspace); + var service = CreateEditAndContinueService(workspace); - var debuggingSession = StartDebuggingSession(service); + var debuggingSession = StartDebuggingSession(service); - StartEditSession(service); + StartEditSession(service); - // change the source (compilation error): - var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { ")); - var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + // change the source (compilation error): + var document1 = workspace.CurrentSolution.Projects.Single().Documents.Single(); + workspace.ChangeDocument(document1.Id, SourceText.From("class C1 { void M() { ")); + var document2 = workspace.CurrentSolution.Projects.Single().Documents.Single(); - // compilation errors are not reported via EnC diagnostic analyzer: - var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None).ConfigureAwait(false); - AssertEx.Empty(diagnostics1); + // compilation errors are not reported via EnC diagnostic analyzer: + var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noDocumentActiveSpans, CancellationToken.None).ConfigureAwait(false); + AssertEx.Empty(diagnostics1); - // validate solution update status and emit: - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None).ConfigureAwait(false)); + // validate solution update status and emit: + Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None).ConfigureAwait(false)); - var (updates, emitDiagnostics) = await service.EmitSolutionUpdateAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, CancellationToken.None).ConfigureAwait(false); - Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); - Assert.Empty(updates.Updates); - Assert.Empty(emitDiagnostics); + var (updates, emitDiagnostics) = await service.EmitSolutionUpdateAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, CancellationToken.None).ConfigureAwait(false); + Assert.Equal(ManagedModuleUpdateStatus.Blocked, updates.Status); + Assert.Empty(updates.Updates); + Assert.Empty(emitDiagnostics); - EndEditSession(service); - EndDebuggingSession(service); + EndEditSession(service); + EndDebuggingSession(service); - AssertEx.SetEqual(new[] { moduleId }, debuggingSession.Test_GetModulesPreparedForUpdate()); + AssertEx.SetEqual(new[] { moduleId }, debuggingSession.Test_GetModulesPreparedForUpdate()); - AssertEx.Equal(new[] - { - "Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0", - "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=True|HadRudeEdits=False|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0" - }, _telemetryLog); - } + AssertEx.Equal(new[] + { + "Debugging_EncSession: SessionId=1|SessionCount=1|EmptySessionCount=0", + "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=True|HadRudeEdits=False|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0" + }, _telemetryLog); } [Fact] @@ -1459,40 +1498,38 @@ public async Task BreakMode_SemanticError() [Fact] public async Task BreakMode_FileStatus_CompilationError() { - using (var workspace = CreateWorkspace()) - { - workspace.ChangeSolution(workspace.CurrentSolution. - AddProject("A", "A", "C#"). - AddDocument("A.cs", "class Program { void Main() { System.Console.WriteLine(1); } }", filePath: "A.cs").Project.Solution. - AddProject("B", "B", "C#"). - AddDocument("Common.cs", "class Common {}", filePath: "Common.cs").Project. - AddDocument("B.cs", "class B {}", filePath: "B.cs").Project.Solution. - AddProject("C", "C", "C#"). - AddDocument("Common.cs", "class Common {}", filePath: "Common.cs").Project. - AddDocument("C.cs", "class C {}", filePath: "C.cs").Project.Solution); + using var workspace = CreateWorkspace(); + workspace.ChangeSolution(workspace.CurrentSolution. + AddProject("A", "A", "C#"). + AddDocument("A.cs", "class Program { void Main() { System.Console.WriteLine(1); } }", filePath: "A.cs").Project.Solution. + AddProject("B", "B", "C#"). + AddDocument("Common.cs", "class Common {}", filePath: "Common.cs").Project. + AddDocument("B.cs", "class B {}", filePath: "B.cs").Project.Solution. + AddProject("C", "C", "C#"). + AddDocument("Common.cs", "class Common {}", filePath: "Common.cs").Project. + AddDocument("C.cs", "class C {}", filePath: "C.cs").Project.Solution); - var service = CreateEditAndContinueService(workspace); + var service = CreateEditAndContinueService(workspace); - StartDebuggingSession(service); - StartEditSession(service); + StartDebuggingSession(service); + StartEditSession(service); - // change C.cs to have a compilation error: - var projectC = workspace.CurrentSolution.GetProjectsByName("C").Single(); - var documentC = projectC.Documents.Single(d => d.Name == "C.cs"); - workspace.ChangeDocument(documentC.Id, SourceText.From("class C { void M() { ")); + // change C.cs to have a compilation error: + var projectC = workspace.CurrentSolution.GetProjectsByName("C").Single(); + var documentC = projectC.Documents.Single(d => d.Name == "C.cs"); + workspace.ChangeDocument(documentC.Id, SourceText.From("class C { void M() { ")); - // Common.cs is included in projects B and C. Both of these projects must have no errors, otherwise update is blocked. - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: "Common.cs", CancellationToken.None).ConfigureAwait(false)); + // Common.cs is included in projects B and C. Both of these projects must have no errors, otherwise update is blocked. + Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: "Common.cs", CancellationToken.None).ConfigureAwait(false)); - // No changes in project containing file B.cs. - Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: "B.cs", CancellationToken.None).ConfigureAwait(false)); + // No changes in project containing file B.cs. + Assert.False(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: "B.cs", CancellationToken.None).ConfigureAwait(false)); - // All projects must have no errors. - Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None).ConfigureAwait(false)); + // All projects must have no errors. + Assert.True(await service.HasChangesAsync(workspace.CurrentSolution, s_noSolutionActiveSpans, sourceFilePath: null, CancellationToken.None).ConfigureAwait(false)); - EndEditSession(service); - EndDebuggingSession(service); - } + EndEditSession(service); + EndDebuggingSession(service); } [Fact] diff --git a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb index 46547f0187035..9983601aab8b1 100644 --- a/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb +++ b/src/EditorFeatures/Test2/IntelliSense/CSharpCompletionCommandHandlerTests.vb @@ -8521,5 +8521,72 @@ class C context.AddItem(CompletionItem.Create(displayText:="ā˜… Length2", filterText:="Length")) End Function End Class + + + + + Public Async Function TestCaseSensitiveMatchWithLowerMatchPriority(showCompletionInArgumentLists As Boolean) As Task + ' PreselectionProvider will provide an item "ā˜… length" with filter text "length", + ' which is a case-insentive match to typed text "Length", but with higher match priority. + ' In this case, we need to make sure the case-sensitive match "Length" is selected. + Using state = TestStateFactory.CreateCSharpTestState( + +struct Range +{ + public (int Offset, int Length) GetOffsetAndLength(int length) => (0, 0); +} + +class Repro +{ + public int Length { get; } + + public void Test(Range x) + { + var (offset, length) = x.GetOffsetAndLength(Length$$); + } +} + , + extraExportedTypes:={GetType(PreselectionProvider)}.ToList(), + showCompletionInArgumentLists:=showCompletionInArgumentLists) + + Dim workspace = state.Workspace + workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options _ + .WithChangedOption(CompletionOptions.TriggerOnDeletion, LanguageNames.CSharp, True))) + + state.SendInvokeCompletionList() + Await state.AssertCompletionItemsContainAll({"ā˜… length", "length", "Length"}) + Await state.AssertSelectedCompletionItem("Length", isHardSelected:=True) + state.SendEscape() + Await state.AssertNoCompletionSession() + + state.SendBackspace() + Await state.AssertCompletionSession() + Await state.AssertCompletionItemsContainAll({"ā˜… length", "length", "Length"}) + Await state.AssertSelectedCompletionItem("Length", isHardSelected:=True) + End Using + End Function + + ' Simulate the situation that some provider (e.g. IntelliCode) provides items with higher match priority that only match case-insensitively. + + <[Shared]> + + Private Class PreselectionProvider + Inherits CommonCompletionProvider + + + + Public Sub New() + End Sub + + Public Overrides Function ProvideCompletionsAsync(context As CompletionContext) As Task + Dim rules = CompletionItemRules.Default.WithSelectionBehavior(CompletionItemSelectionBehavior.HardSelection).WithMatchPriority(MatchPriority.Preselect) + context.AddItem(CompletionItem.Create(displayText:="ā˜… length", filterText:="length", rules:=rules)) + Return Task.CompletedTask + End Function + + Public Overrides Function IsInsertionTrigger(text As SourceText, characterPosition As Integer, options As OptionSet) As Boolean + Return True + End Function + End Class End Class End Namespace diff --git a/src/Features/Core/Portable/Completion/CompletionHelper.cs b/src/Features/Core/Portable/Completion/CompletionHelper.cs index 0b13368f3ea47..a06e5d8669467 100644 --- a/src/Features/Core/Portable/Completion/CompletionHelper.cs +++ b/src/Features/Core/Portable/Completion/CompletionHelper.cs @@ -250,10 +250,18 @@ private int CompareMatches(PatternMatch match1, PatternMatch match2, CompletionI return diff; } - var preselectionDiff = ComparePreselection(item1, item2); - if (preselectionDiff != 0) + // If two items match in case-insensitive manner, and we are in a case-insensitive language, + // then the preselected one is considered better, otherwise we will prefer the one matches + // case-sensitively. This is to make sure common items in VB like `True` and `False` are prioritized + // for selection when user types `t` and `f`. + // More details can be found in comments of https://github.com/dotnet/roslyn/issues/4892 + if (!_isCaseSensitive) { - return preselectionDiff; + var preselectionDiff = ComparePreselection(item1, item2); + if (preselectionDiff != 0) + { + return preselectionDiff; + } } // At this point we have two items which we're matching in a rather similar fashion. diff --git a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs index 63b605b2b6dd0..be8b57266d566 100644 --- a/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs +++ b/src/Features/Core/Portable/EditAndContinue/AbstractEditAndContinueAnalyzer.cs @@ -458,7 +458,7 @@ public async Task AnalyzeDocumentAsync( SyntaxNode oldRoot; SourceText oldText; - var oldDocument = oldProject.GetDocument(newDocument.Id); + var oldDocument = await oldProject.GetDocumentAsync(newDocument.Id, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); if (oldDocument != null) { oldTree = await oldDocument.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpGoToDefinition.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpGoToDefinition.cs index 433e7b05d9ffd..23bd51b9c310d 100644 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpGoToDefinition.cs +++ b/src/VisualStudio/IntegrationTest/IntegrationTests/CSharp/CSharpGoToDefinition.cs @@ -26,7 +26,7 @@ public CSharpGoToDefinition(VisualStudioInstanceFactory instanceFactory) { } - [WpfFact, Trait(Traits.Feature, Traits.Features.GoToDefinition)] + [WpfFact, Trait(Traits.Feature, Traits.Features.GoToDefinition), Trait(Traits.Editor, Traits.Editors.LanguageServerProtocol)] public void GoToClassDeclaration() { var project = new ProjectUtils.Project(ProjectName); @@ -49,7 +49,7 @@ public void GoToClassDeclaration() Assert.False(VisualStudio.Shell.IsActiveTabProvisional()); } - [WpfFact, Trait(Traits.Feature, Traits.Features.GoToDefinition)] + [WpfFact, Trait(Traits.Feature, Traits.Features.GoToDefinition), Trait(Traits.Editor, Traits.Editors.LanguageServerProtocol)] public void GoToDefinitionOpensProvisionalTabIfDocumentNotAlreadyOpen() { var project = new ProjectUtils.Project(ProjectName); @@ -74,19 +74,16 @@ public void GoToDefinitionOpensProvisionalTabIfDocumentNotAlreadyOpen() Assert.True(VisualStudio.Shell.IsActiveTabProvisional()); } - [WpfFact, Trait(Traits.Feature, Traits.Features.GoToDefinition)] + [WpfFact, Trait(Traits.Feature, Traits.Features.GoToDefinition), Trait(Traits.Editor, Traits.Editors.LanguageServerProtocol)] public virtual void GoToDefinitionWithMultipleResults() - { - TestGoToDefinitionWithMultipleResults(declarationWindowName: "'PartialClass' declarations"); - } - - protected void TestGoToDefinitionWithMultipleResults(string declarationWindowName) { SetUpEditor( @"partial class /*Marker*/ $$PartialClass { } partial class PartialClass { int i = 0; }"); + var declarationWindowName = VisualStudio.IsUsingLspEditor ? "'PartialClass' references" : "'PartialClass' declarations"; + VisualStudio.Editor.GoToDefinition(declarationWindowName); var results = VisualStudio.FindReferencesWindow.GetContents(declarationWindowName); diff --git a/src/VisualStudio/IntegrationTest/IntegrationTests/LanguageServerProtocol/LspGoToDefinition.cs b/src/VisualStudio/IntegrationTest/IntegrationTests/LanguageServerProtocol/LspGoToDefinition.cs deleted file mode 100644 index 5c16894e6082d..0000000000000 --- a/src/VisualStudio/IntegrationTest/IntegrationTests/LanguageServerProtocol/LspGoToDefinition.cs +++ /dev/null @@ -1,42 +0,0 @@ -ļ»æ// 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. - -using System; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.IntegrationTest.Utilities; -using Microsoft.VisualStudio.IntegrationTest.Utilities.Common; -using Roslyn.Test.Utilities; -using Roslyn.VisualStudio.IntegrationTests.CSharp; -using Xunit; - -namespace Roslyn.VisualStudio.IntegrationTests.LanguageServerProtocol -{ - /// - /// A set of LSP specific goto definition tests. - /// These tests test behavior that only applies to the LSP version of goto definition. - /// - [Collection(nameof(SharedIntegrationHostFixture))] - [Trait(Traits.Editor, Traits.Editors.LanguageServerProtocol)] - public class LspGoToDefinition : CSharpGoToDefinition - { - protected override string LanguageName => LanguageNames.CSharp; - - public LspGoToDefinition(VisualStudioInstanceFactory instanceFactory) - : base(instanceFactory) - { - } - - /// - /// We need to pass in a different window name to look for declarations in as the LSP client - /// uses a different name to create the references window. - /// https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1286575 - /// - [WpfFact, Trait(Traits.Feature, Traits.Features.GoToDefinition), Trait(Traits.Editor, Traits.Editors.LanguageServerProtocol)] - public override void GoToDefinitionWithMultipleResults() - { - TestGoToDefinitionWithMultipleResults(declarationWindowName: "'PartialClass' references"); - } - } -} diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs index 6918f32af99b9..b4b41f6c123e7 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstance.cs @@ -91,12 +91,15 @@ public class VisualStudioInstance /// public string InstallationPath { get; } - public VisualStudioInstance(Process hostProcess, DTE dte, ImmutableHashSet supportedPackageIds, string installationPath) + public bool IsUsingLspEditor { get; } + + public VisualStudioInstance(Process hostProcess, DTE dte, ImmutableHashSet supportedPackageIds, string installationPath, bool isUsingLspEditor) { HostProcess = hostProcess; Dte = dte; SupportedPackageIds = supportedPackageIds; InstallationPath = installationPath; + IsUsingLspEditor = isUsingLspEditor; if (System.Diagnostics.Debugger.IsAttached) { diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs index db3d13b20ca34..0a54d6109baa8 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/VisualStudioInstanceFactory.cs @@ -167,6 +167,8 @@ private async Task UpdateCurrentlyRunningInstanceAsync(ImmutableHashSet ImmutableHashSet supportedPackageIds; string installationPath; + var isUsingLspEditor = IsUsingLspEditor(); + if (shouldStartNewInstance) { // We are starting a new instance, so ensure we close the currently running instance, if it exists @@ -178,7 +180,7 @@ private async Task UpdateCurrentlyRunningInstanceAsync(ImmutableHashSet var instanceVersion = instance.GetInstallationVersion(); var majorVersion = int.Parse(instanceVersion.Substring(0, instanceVersion.IndexOf('.'))); - hostProcess = StartNewVisualStudioProcess(installationPath, majorVersion); + hostProcess = StartNewVisualStudioProcess(installationPath, majorVersion, isUsingLspEditor); var procDumpInfo = ProcDumpInfo.ReadFromEnvironment(); if (procDumpInfo != null) @@ -208,7 +210,7 @@ private async Task UpdateCurrentlyRunningInstanceAsync(ImmutableHashSet _currentlyRunningInstance.Close(exitHostProcess: false); } - _currentlyRunningInstance = new VisualStudioInstance(hostProcess, dte, supportedPackageIds, installationPath); + _currentlyRunningInstance = new VisualStudioInstance(hostProcess, dte, supportedPackageIds, installationPath, isUsingLspEditor); } private static IEnumerable EnumerateVisualStudioInstances() @@ -308,13 +310,11 @@ private static ISetupInstance LocateVisualStudioInstance(ImmutableHashSet