From 4e5e7dd250e319d9fc23aa5fce358361d1c603e1 Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Fri, 5 Apr 2024 22:34:04 +0300 Subject: [PATCH 01/28] Use semantics to check for readonly struct --- ...ctCSharpAutoPropertyCompletionProviderTests.cs | 3 +++ .../CSharpPropSnippetCompletionProviderTests.cs | 15 +++++++++++++++ .../CSharpPropgSnippetCompletionProviderTests.cs | 15 +++++++++++++++ .../CSharpPropiSnippetCompletionProviderTests.cs | 14 ++++++++++++++ .../AbstractCSharpAutoPropertySnippetProvider.cs | 8 ++++---- .../Snippets/CSharpPropSnippetProvider.cs | 7 ++++--- .../Snippets/CSharpPropgSnippetProvider.cs | 5 +++-- .../Snippets/CSharpPropiSnippetProvider.cs | 3 ++- 8 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpAutoPropertyCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpAutoPropertyCompletionProviderTests.cs index 0f5cb25a7399..553d05433e4f 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpAutoPropertyCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpAutoPropertyCompletionProviderTests.cs @@ -80,6 +80,9 @@ struct MyStruct [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] public abstract Task InsertSnippetInReadonlyStruct(); + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public abstract Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration(); + // This case might produce non-default results for different snippets (e.g. no `set` accessor in 'propg' snippet), // so it is tested separately for all of them [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropSnippetCompletionProviderTests.cs index 056e7a9dc280..79af1af7603d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropSnippetCompletionProviderTests.cs @@ -24,6 +24,21 @@ readonly struct MyStruct """, "public int MyProperty { get; }"); } + public override async Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration() + { + // Ensure we don't generate redundant `set` accessor when executed in readonly struct + await VerifyPropertyAsync(""" + partial struct MyStruct + { + $$ + } + + readonly partial struct MyStruct + { + } + """, "public int MyProperty { get; }"); + } + public override async Task InsertSnippetInInterface() { await VerifyDefaultPropertyAsync(""" diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropgSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropgSnippetCompletionProviderTests.cs index 581c87cf7a82..752d73603d37 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropgSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropgSnippetCompletionProviderTests.cs @@ -24,6 +24,21 @@ readonly struct MyStruct """, "public int MyProperty { get; }"); } + public override async Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration() + { + // Ensure we don't generate redundant `set` accessor when executed in readonly struct + await VerifyPropertyAsync(""" + partial struct MyStruct + { + $$ + } + + readonly partial struct MyStruct + { + } + """, "public int MyProperty { get; }"); + } + public override async Task InsertSnippetInInterface() { // Ensure we don't generate redundant `set` accessor when executed in interface diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropiSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropiSnippetCompletionProviderTests.cs index bd63e688a321..44c738820f09 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropiSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropiSnippetCompletionProviderTests.cs @@ -23,6 +23,20 @@ readonly struct MyStruct """); } + public override async Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration() + { + await VerifyDefaultPropertyAsync(""" + partial struct MyStruct + { + $$ + } + + readonly partial struct MyStruct + { + } + """); + } + public override async Task InsertSnippetInInterface() { await VerifyDefaultPropertyAsync(""" diff --git a/src/Features/CSharp/Portable/Snippets/AbstractCSharpAutoPropertySnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/AbstractCSharpAutoPropertySnippetProvider.cs index bcc6c780bf9e..50632017e6f0 100644 --- a/src/Features/CSharp/Portable/Snippets/AbstractCSharpAutoPropertySnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/AbstractCSharpAutoPropertySnippetProvider.cs @@ -24,10 +24,10 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets; internal abstract class AbstractCSharpAutoPropertySnippetProvider : AbstractPropertySnippetProvider { - protected virtual AccessorDeclarationSyntax? GenerateGetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator) + protected virtual AccessorDeclarationSyntax? GenerateGetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator, CancellationToken cancellationToken) => (AccessorDeclarationSyntax)generator.GetAccessorDeclaration(); - protected virtual AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator) + protected virtual AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator, CancellationToken cancellationToken) => (AccessorDeclarationSyntax)generator.SetAccessorDeclaration(); protected override bool IsValidSnippetLocation(in SnippetContext context, CancellationToken cancellationToken) @@ -46,8 +46,8 @@ protected override async Task GenerateSnippetSyntaxAsync(Document do var syntaxContext = CSharpSyntaxContext.CreateContext(document, semanticModel, position, cancellationToken); var accessors = new AccessorDeclarationSyntax?[] { - GenerateGetAccessorDeclaration(syntaxContext, generator), - GenerateSetAccessorDeclaration(syntaxContext, generator), + GenerateGetAccessorDeclaration(syntaxContext, generator, cancellationToken), + GenerateSetAccessorDeclaration(syntaxContext, generator, cancellationToken), }; SyntaxTokenList modifiers = default; diff --git a/src/Features/CSharp/Portable/Snippets/CSharpPropSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpPropSnippetProvider.cs index 64d2ab7a1a40..98ff8070df3e 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpPropSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpPropSnippetProvider.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; @@ -22,16 +23,16 @@ internal sealed class CSharpPropSnippetProvider() : AbstractCSharpAutoPropertySn public override string Description => FeaturesResources.property_; - protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator) + protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator, CancellationToken cancellationToken) { // Having a property with `set` accessor in a readonly struct leads to a compiler error. // So if user executes snippet inside a readonly struct the right thing to do is to not generate `set` accessor at all if (syntaxContext.ContainingTypeDeclaration is StructDeclarationSyntax structDeclaration && - structDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword)) + syntaxContext.SemanticModel.GetDeclaredSymbol(structDeclaration, cancellationToken) is { IsReadOnly: true }) { return null; } - return base.GenerateSetAccessorDeclaration(syntaxContext, generator); + return base.GenerateSetAccessorDeclaration(syntaxContext, generator, cancellationToken); } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpPropgSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpPropgSnippetProvider.cs index 5bc0832910a7..e574f05665d7 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpPropgSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpPropgSnippetProvider.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; @@ -22,7 +23,7 @@ internal sealed class CSharpPropgSnippetProvider() : AbstractCSharpAutoPropertyS public override string Description => FeaturesResources.get_only_property; - protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator) + protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator, CancellationToken cancellationToken) { // Interface cannot have properties with `private set` accessor. // So if we are inside an interface, we just return null here. @@ -35,7 +36,7 @@ internal sealed class CSharpPropgSnippetProvider() : AbstractCSharpAutoPropertyS // Having a property with `set` accessor in a readonly struct leads to a compiler error. // So if user executes snippet inside a readonly struct the right thing to do is to not generate `set` accessor at all if (syntaxContext.ContainingTypeDeclaration is StructDeclarationSyntax structDeclaration && - structDeclaration.Modifiers.Any(SyntaxKind.ReadOnlyKeyword)) + syntaxContext.SemanticModel.GetDeclaredSymbol(structDeclaration, cancellationToken) is { IsReadOnly: true }) { return null; } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpPropiSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpPropiSnippetProvider.cs index c73393afc4d1..8bd6667edfcf 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpPropiSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpPropiSnippetProvider.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; @@ -22,6 +23,6 @@ internal sealed class CSharpPropiSnippetProvider() : AbstractCSharpAutoPropertyS public override string Description => CSharpFeaturesResources.init_only_property; - protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator) + protected override AccessorDeclarationSyntax? GenerateSetAccessorDeclaration(CSharpSyntaxContext syntaxContext, SyntaxGenerator generator, CancellationToken cancellationToken) => SyntaxFactory.AccessorDeclaration(SyntaxKind.InitAccessorDeclaration).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); } From 52196dc71b0b511e85288bf86fbac12b9c2a987b Mon Sep 17 00:00:00 2001 From: DoctorKrolic Date: Sat, 13 Apr 2024 17:25:16 +0300 Subject: [PATCH 02/28] Add tests with missing `partial` modifier --- ...SharpAutoPropertyCompletionProviderTests.cs | 3 +++ ...CSharpPropSnippetCompletionProviderTests.cs | 18 ++++++++++++++++++ ...SharpPropgSnippetCompletionProviderTests.cs | 18 ++++++++++++++++++ ...SharpPropiSnippetCompletionProviderTests.cs | 14 ++++++++++++++ 4 files changed, 53 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpAutoPropertyCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpAutoPropertyCompletionProviderTests.cs index 553d05433e4f..9c9924a36133 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpAutoPropertyCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpAutoPropertyCompletionProviderTests.cs @@ -83,6 +83,9 @@ struct MyStruct [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] public abstract Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration(); + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public abstract Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration_MissingPartialModifier(); + // This case might produce non-default results for different snippets (e.g. no `set` accessor in 'propg' snippet), // so it is tested separately for all of them [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropSnippetCompletionProviderTests.cs index 79af1af7603d..e37cb7f8ac5c 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropSnippetCompletionProviderTests.cs @@ -39,6 +39,24 @@ readonly partial struct MyStruct """, "public int MyProperty { get; }"); } + public override async Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration_MissingPartialModifier() + { + // Even though there is no `partial` modifier on the first declaration + // compiler still treats the whole type as partial since it is more likely that + // the user's intent was to have a partial type and they just forgot the modifier. + // Thus we still recognize that as `readonly` context and don't generate a setter + await VerifyPropertyAsync(""" + struct MyStruct + { + $$ + } + + readonly partial struct MyStruct + { + } + """, "public int MyProperty { get; }"); + } + public override async Task InsertSnippetInInterface() { await VerifyDefaultPropertyAsync(""" diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropgSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropgSnippetCompletionProviderTests.cs index 752d73603d37..b71b656a0dc6 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropgSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropgSnippetCompletionProviderTests.cs @@ -39,6 +39,24 @@ readonly partial struct MyStruct """, "public int MyProperty { get; }"); } + public override async Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration_MissingPartialModifier() + { + // Even though there is no `partial` modifier on the first declaration + // compiler still treats the whole type as partial since it is more likely that + // the user's intent was to have a partial type and they just forgot the modifier. + // Thus we still recognize that as `readonly` context and don't generate a setter + await VerifyPropertyAsync(""" + struct MyStruct + { + $$ + } + + readonly partial struct MyStruct + { + } + """, "public int MyProperty { get; }"); + } + public override async Task InsertSnippetInInterface() { // Ensure we don't generate redundant `set` accessor when executed in interface diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropiSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropiSnippetCompletionProviderTests.cs index 44c738820f09..ea99fb25ef2d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropiSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpPropiSnippetCompletionProviderTests.cs @@ -37,6 +37,20 @@ readonly partial struct MyStruct """); } + public override async Task InsertSnippetInReadonlyStruct_ReadonlyModifierInOtherPartialDeclaration_MissingPartialModifier() + { + await VerifyDefaultPropertyAsync(""" + struct MyStruct + { + $$ + } + + readonly partial struct MyStruct + { + } + """); + } + public override async Task InsertSnippetInInterface() { await VerifyDefaultPropertyAsync(""" From 91ab06c1dfad4608fcf399e467bddec0a2bb1e53 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 21 May 2024 15:45:36 +1000 Subject: [PATCH 03/28] Allow Razor to access LSP types through IVT --- .../Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj index 6db127bd2f72..3af6709eb8d2 100644 --- a/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj +++ b/src/Features/LanguageServer/Protocol/Microsoft.CodeAnalysis.LanguageServer.Protocol.csproj @@ -34,7 +34,11 @@ + + + + From ea165649212439f728cfbb954500b2fd0a6849b6 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 21 May 2024 15:45:46 +1000 Subject: [PATCH 04/28] Future proof json formatters --- .../ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tools/ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs b/src/Tools/ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs index 81cfa6493e4c..78104699f3f6 100644 --- a/src/Tools/ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs +++ b/src/Tools/ExternalAccess/Razor/RazorLanguageServerFactoryWrapper.cs @@ -67,7 +67,7 @@ internal override DocumentInfo CreateDocumentInfo( internal override void AddJsonConverters(JsonSerializerOptions options) { - VSInternalExtensionUtilities.AddVSInternalExtensionConverters(options); + ProtocolConversions.AddLspSerializerOptions(options); } private class RazorCapabilitiesProvider : ICapabilitiesProvider From 13fbbc26afa670c2b07ffe022ca5263de8c714cd Mon Sep 17 00:00:00 2001 From: David Wengier Date: Tue, 21 May 2024 15:45:56 +1000 Subject: [PATCH 05/28] Expose folding ranges functionality to Razor --- .../FoldingRanges/FoldingRangesHandler.cs | 17 ++++++++++--- .../Razor/Cohost/Handlers/FoldingRanges.cs | 25 +++++++++++++++++++ 2 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 src/Tools/ExternalAccess/Razor/Cohost/Handlers/FoldingRanges.cs diff --git a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs index 27f5ede88db8..ee5b92c5343e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/FoldingRanges/FoldingRangesHandler.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Structure; using Microsoft.CodeAnalysis.Text; using Roslyn.LanguageServer.Protocol; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.LanguageServer.Handler { @@ -35,13 +36,21 @@ public FoldingRangesHandler(IGlobalOptionService globalOptions) public TextDocumentIdentifier GetTextDocumentIdentifier(FoldingRangeParams request) => request.TextDocument; - public async Task HandleRequestAsync(FoldingRangeParams request, RequestContext context, CancellationToken cancellationToken) + public Task HandleRequestAsync(FoldingRangeParams request, RequestContext context, CancellationToken cancellationToken) { var document = context.Document; if (document is null) - return null; + return SpecializedTasks.Null(); - var options = _globalOptions.GetBlockStructureOptions(document.Project) with + return SpecializedTasks.AsNullable(GetFoldingRangesAsync(_globalOptions, document, cancellationToken)); + } + + internal static Task GetFoldingRangesAsync( + IGlobalOptionService globalOptions, + Document document, + CancellationToken cancellationToken) + { + var options = globalOptions.GetBlockStructureOptions(document.Project) with { // Need to set the block structure guide options to true since the concept does not exist in vscode // but we still want to categorize them as the correct BlockType. @@ -50,7 +59,7 @@ public FoldingRangesHandler(IGlobalOptionService globalOptions) ShowBlockStructureGuidesForCodeLevelConstructs = true }; - return await GetFoldingRangesAsync(document, options, cancellationToken).ConfigureAwait(false); + return GetFoldingRangesAsync(document, options, cancellationToken); } /// diff --git a/src/Tools/ExternalAccess/Razor/Cohost/Handlers/FoldingRanges.cs b/src/Tools/ExternalAccess/Razor/Cohost/Handlers/FoldingRanges.cs new file mode 100644 index 000000000000..200e40c7eccd --- /dev/null +++ b/src/Tools/ExternalAccess/Razor/Cohost/Handlers/FoldingRanges.cs @@ -0,0 +1,25 @@ +// 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.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.LanguageServer.Handler; +using Microsoft.CodeAnalysis.Options; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers +{ + internal static class FoldingRanges + { + public static Task GetFoldingRangesAsync(Document document, CancellationToken cancellationToken) + { + // We need to manually get the IGlobalOptionsService out of the Mef composition, because Razor has its own + // composition so can't import it (and its internal anyway) + var globalOptions = document.Project.Solution.Services.ExportProvider.GetExports().First().Value; + + return FoldingRangesHandler.GetFoldingRangesAsync(globalOptions, document, cancellationToken); + } + } +} From 72f2fd3b720613f1bb0a74770d0f2e1d4195721f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 12:29:32 -0700 Subject: [PATCH 06/28] Switch SG mode to 'balanced' by default --- .../Rename/CSharp/SourceGeneratorTests.vb | 1 - .../Test2/Rename/RenameEngineResult.vb | 7 ++++--- .../WorkspaceConfigurationOptionsStorage.cs | 2 +- .../SourceGeneratorItemTests.vb | 18 ++++++++++++++++-- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb b/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb index 96808a907ff2..8ebc28eb5e83 100644 --- a/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb +++ b/src/EditorFeatures/Test2/Rename/CSharp/SourceGeneratorTests.vb @@ -72,7 +72,6 @@ public partial class GeneratedClass : IInterface { } , host:=host, renameTo:="A", sourceGenerator:=New GeneratorThatImplementsInterfaceMethod()) - End Using End Sub diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index 7cc7b5cb3537..3cfdffe90f3d 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -6,10 +6,8 @@ Imports System.Collections.Immutable Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.CodeActions -Imports Microsoft.CodeAnalysis.CodeCleanup Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Host -Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Remote.Testing Imports Microsoft.CodeAnalysis.Rename Imports Microsoft.CodeAnalysis.Rename.ConflictEngine @@ -62,7 +60,8 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Dim composition = EditorTestCompositions.EditorFeatures.AddParts( GetType(NoCompilationContentTypeLanguageService), GetType(NoCompilationContentTypeDefinitions), - GetType(WorkspaceTestLogger)) + GetType(WorkspaceTestLogger), + GetType(TestWorkspaceConfigurationService)) If host = RenameTestHost.OutOfProcess_SingleCall OrElse host = RenameTestHost.OutOfProcess_SplitCall Then composition = composition.WithTestHostParts(TestHost.OutOfProcess) @@ -70,6 +69,8 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Dim workspace = TestWorkspace.CreateWorkspace(workspaceXml, composition:=composition) workspace.Services.SolutionServices.SetWorkspaceTestOutput(helper) + workspace.GlobalOptions.SetGlobalOption( + WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic) If sourceGenerator IsNot Nothing Then workspace.OnAnalyzerReferenceAdded(workspace.CurrentSolution.ProjectIds.Single(), New TestGeneratorReference(sourceGenerator)) diff --git a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs index 773b736d0cb2..514450460e7f 100644 --- a/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs +++ b/src/Features/LanguageServer/Protocol/Features/Options/WorkspaceConfigurationOptionsStorage.cs @@ -50,5 +50,5 @@ public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(thi SourceGeneratorExecutionPreferenceUtilities.GetEditorConfigString)); public static readonly Option2 SourceGeneratorExecutionBalancedFeatureFlag = new( - "dotnet_source_generator_execution_balanced_feature_flag", false); + "dotnet_source_generator_execution_balanced_feature_flag", true); } diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index 75edd90c0dd4..d475e6a87c8e 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -5,7 +5,9 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities +Imports Microsoft.CodeAnalysis.Editor.UnitTests Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces +Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.CodeAnalysis.Text @@ -131,7 +133,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer - Using workspace = EditorTestWorkspace.Create(workspaceXml) + Using workspace = EditorTestWorkspace.Create( + workspaceXml, + composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(TestWorkspaceConfigurationService))) + + workspace.GlobalOptions.SetGlobalOption( + WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic) + Dim projectId = workspace.Projects.Single().Id Dim source = CreateItemSourceForAnalyzerReference(workspace, projectId) Dim generatorItem = Assert.IsAssignableFrom(Of SourceGeneratorItem)(Assert.Single(source.Items)) @@ -159,7 +167,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer - Using workspace = EditorTestWorkspace.Create(workspaceXml) + Using workspace = EditorTestWorkspace.Create( + workspaceXml, + composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(TestWorkspaceConfigurationService))) + + workspace.GlobalOptions.SetGlobalOption( + WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic) + Dim projectId = workspace.Projects.Single().Id Dim source = CreateItemSourceForAnalyzerReference(workspace, projectId) Dim generatorItem = Assert.IsAssignableFrom(Of SourceGeneratorItem)(Assert.Single(source.Items)) From ea95ad5ac2bef8c6a0090f62d9c29e5c7427dc1c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 13:34:20 -0700 Subject: [PATCH 07/28] Fix test --- src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb | 6 ++---- src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb index e6b354035207..19f53caf2372 100644 --- a/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb +++ b/src/EditorFeatures/Test2/Rename/RenameTestHelpers.vb @@ -10,10 +10,8 @@ Imports Microsoft.CodeAnalysis.Editor.Implementation.RenameTracking Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests.RenameTracking Imports Microsoft.CodeAnalysis.Editor.UnitTests.Utilities.GoToHelpers -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Shared.TestHooks -Imports Microsoft.CodeAnalysis.Rename Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.Text.Shared.Extensions Imports Microsoft.VisualStudio.Text @@ -22,10 +20,10 @@ Imports Microsoft.VisualStudio.Text.Tagging Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Friend Module RenameTestHelpers - Private ReadOnly s_composition As TestComposition = EditorTestCompositions.EditorFeaturesWpf.AddParts( GetType(MockDocumentNavigationServiceFactory), - GetType(MockPreviewDialogService)) + GetType(MockPreviewDialogService), + GetType(TestWorkspaceConfigurationService)) Private Function GetSessionInfo(workspace As EditorTestWorkspace) As (document As Document, textSpan As TextSpan) Dim hostdoc = workspace.DocumentWithCursor diff --git a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb index c86ff38b884b..e406a633b70e 100644 --- a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb @@ -6,6 +6,7 @@ Imports System.Threading Imports Microsoft.CodeAnalysis.Editor.Implementation.InlineRename Imports Microsoft.CodeAnalysis.Editor.InlineRename Imports Microsoft.CodeAnalysis.Editor.[Shared].Utilities +Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.InlineRename Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.[Shared].TestHooks @@ -556,6 +557,7 @@ class D : B globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInStrings, renameInStrings) globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInComments, renameInComments) globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameFile, renameFile) + globalOptions.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic) Dim cursorDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) Dim cursorPosition = cursorDocument.CursorPosition.Value From 90faf362efa7da7a168f38fc468f94a1cc7cd47a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 13:38:12 -0700 Subject: [PATCH 08/28] fix tests --- .../Workspaces/WorkspaceTests_EditorFeatures.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index a04d587c91a7..a7db0c6c2340 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -17,6 +17,7 @@ using Microsoft.CodeAnalysis.Editor.UnitTests; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -471,7 +472,11 @@ End Class [Fact] public async Task TestGetCompilationOnCrossLanguageDependentProjectChanged() { - using var workspace = CreateWorkspace(); + using var workspace = CreateWorkspace(composition: EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceConfigurationService))); + + var options = workspace.ExportProvider.GetExportedValue(); + options.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic); + var solutionX = workspace.CurrentSolution; var document1 = new EditorTestHostDocument(@"public class C { }"); @@ -578,9 +583,15 @@ End Class [WpfFact] public async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInProgress() { - var composition = EditorTestCompositions.EditorFeatures.AddParts(typeof(TestDocumentTrackingService)); + var composition = EditorTestCompositions.EditorFeatures.AddParts( + typeof(TestDocumentTrackingService), + typeof(TestWorkspaceConfigurationService)); using var workspace = CreateWorkspace(disablePartialSolutions: false, composition: composition); + + var options = workspace.ExportProvider.GetExportedValue(); + options.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic); + var trackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); var solutionX = workspace.CurrentSolution; From 70ec054a36217891d2d2795623189ae487e50a85 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 13:41:49 -0700 Subject: [PATCH 09/28] Make into theories --- .../Workspaces/WorkspaceTests_EditorFeatures.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index a7db0c6c2340..2c89636eedb8 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -469,13 +469,14 @@ End Class var classC = classD.BaseType; } - [Fact] - public async Task TestGetCompilationOnCrossLanguageDependentProjectChanged() + [Theory, CombinatorialData] + internal async Task TestGetCompilationOnCrossLanguageDependentProjectChanged( + SourceGeneratorExecutionPreference preference) { using var workspace = CreateWorkspace(composition: EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceConfigurationService))); var options = workspace.ExportProvider.GetExportedValue(); - options.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic); + options.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, preference); var solutionX = workspace.CurrentSolution; @@ -580,8 +581,9 @@ End Class } } - [WpfFact] - public async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInProgress() + [WpfTheory, CombinatorialData] + internal async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInProgress( + SourceGeneratorExecutionPreference preference) { var composition = EditorTestCompositions.EditorFeatures.AddParts( typeof(TestDocumentTrackingService), @@ -590,7 +592,7 @@ public async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInProg using var workspace = CreateWorkspace(disablePartialSolutions: false, composition: composition); var options = workspace.ExportProvider.GetExportedValue(); - options.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic); + options.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, preference); var trackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); var solutionX = workspace.CurrentSolution; From 088f30b2081e7a5f96d2ac751e5e146903484ef7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 13:50:36 -0700 Subject: [PATCH 10/28] Update tests --- .../SourceGeneratorItemTests.vb | 25 +++++++++++-------- .../TestWorkspaceConfigurationService.cs | 24 ++++++------------ 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index d475e6a87c8e..55919acdb7f7 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -6,7 +6,6 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.Shared.Utilities Imports Microsoft.CodeAnalysis.Editor.UnitTests -Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Shared.TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities @@ -124,8 +123,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer End Using End Function - - Public Async Function ChangeToRemoveAllGeneratedDocumentsUpdatesListCorrectly() As Task + + Friend Async Function ChangeToRemoveAllGeneratedDocumentsUpdatesListCorrectly( + preference As SourceGeneratorExecutionPreference) As Task Dim workspaceXml = @@ -137,8 +137,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer workspaceXml, composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(TestWorkspaceConfigurationService))) - workspace.GlobalOptions.SetGlobalOption( - WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic) + Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) + configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=preference) Dim projectId = workspace.Projects.Single().Id Dim source = CreateItemSourceForAnalyzerReference(workspace, projectId) @@ -155,12 +155,17 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Await WaitForGeneratorsAndItemSourcesAsync(workspace) - Assert.IsType(Of NoSourceGeneratedFilesPlaceholderItem)(Assert.Single(generatorFilesItemSource.Items)) + If preference = SourceGeneratorExecutionPreference.Automatic Then + Assert.IsType(Of NoSourceGeneratedFilesPlaceholderItem)(Assert.Single(generatorFilesItemSource.Items)) + Else + Assert.IsType(Of SourceGeneratedFileItem)(Assert.Single(generatorFilesItemSource.Items)) + End If End Using End Function - - Public Async Function AddingAGeneratedDocumentUpdatesListCorrectly() As Task + + Friend Async Function AddingAGeneratedDocumentUpdatesListCorrectly( + preference As SourceGeneratorExecutionPreference) As Task Dim workspaceXml = @@ -171,8 +176,8 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer workspaceXml, composition:=EditorTestCompositions.EditorFeatures.AddParts(GetType(TestWorkspaceConfigurationService))) - workspace.GlobalOptions.SetGlobalOption( - WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic) + Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) + configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=preference) Dim projectId = workspace.Projects.Single().Id Dim source = CreateItemSourceForAnalyzerReference(workspace, projectId) diff --git a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs index 9915049dc04b..c19eed238124 100644 --- a/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs +++ b/src/Workspaces/CoreTestUtilities/Workspaces/TestWorkspaceConfigurationService.cs @@ -4,24 +4,16 @@ using System; using System.Composition; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -namespace Roslyn.Test.Utilities -{ - [Export] - [ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Test)] - [Shared] - [PartNotDiscoverable] - internal sealed class TestWorkspaceConfigurationService : IWorkspaceConfigurationService - { - public WorkspaceConfigurationOptions Options { get; set; } +namespace Roslyn.Test.Utilities; - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestWorkspaceConfigurationService() - { - } - } +[Export, Shared, PartNotDiscoverable] +[ExportWorkspaceService(typeof(IWorkspaceConfigurationService), ServiceLayer.Test)] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class TestWorkspaceConfigurationService() : IWorkspaceConfigurationService +{ + public WorkspaceConfigurationOptions Options { get; set; } } From 4148828842c3393fb4f2e3054d9175eef41298a6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 13:53:33 -0700 Subject: [PATCH 11/28] Update tests --- .../SolutionExplorer/SourceGeneratorItemTests.vb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb index 55919acdb7f7..74145221a205 100644 --- a/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb +++ b/src/VisualStudio/Core/Test/SolutionExplorer/SourceGeneratorItemTests.vb @@ -155,6 +155,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Await WaitForGeneratorsAndItemSourcesAsync(workspace) + ' In balanced-mode the SG file won't go away until a save/build happens. If preference = SourceGeneratorExecutionPreference.Automatic Then Assert.IsType(Of NoSourceGeneratedFilesPlaceholderItem)(Assert.Single(generatorFilesItemSource.Items)) Else @@ -198,7 +199,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer Await WaitForGeneratorsAndItemSourcesAsync(workspace) - Assert.IsType(Of SourceGeneratedFileItem)(Assert.Single(generatorFilesItemSource.Items)) + ' In balanced-mode the SG file won't be created until a save/build happens. + If preference = SourceGeneratorExecutionPreference.Automatic Then + Assert.IsType(Of SourceGeneratedFileItem)(Assert.Single(generatorFilesItemSource.Items)) + Else + Assert.IsType(Of NoSourceGeneratedFilesPlaceholderItem)(Assert.Single(generatorFilesItemSource.Items)) + End If ' Add a second item and see if it updates correctly again workspace.OnAdditionalDocumentAdded( @@ -207,7 +213,12 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.SolutionExplorer "Test2.txt")) Await WaitForGeneratorsAndItemSourcesAsync(workspace) - Assert.Equal(2, generatorFilesItemSource.Items.Cast(Of SourceGeneratedFileItem)().Count()) + + If preference = SourceGeneratorExecutionPreference.Automatic Then + Assert.Equal(2, generatorFilesItemSource.Items.Cast(Of SourceGeneratedFileItem)().Count()) + Else + Assert.Equal(1, generatorFilesItemSource.Items.Cast(Of NoSourceGeneratedFilesPlaceholderItem)().Count()) + End If End Using End Function From b4bac65adfb84b9eeafd1e9a0c0544cf93be356e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 13:58:22 -0700 Subject: [PATCH 12/28] Update tests --- .../WorkspaceTests_EditorFeatures.cs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index 2c89636eedb8..5539aca51eb1 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -475,8 +475,8 @@ internal async Task TestGetCompilationOnCrossLanguageDependentProjectChanged( { using var workspace = CreateWorkspace(composition: EditorTestCompositions.EditorFeatures.AddParts(typeof(TestWorkspaceConfigurationService))); - var options = workspace.ExportProvider.GetExportedValue(); - options.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, preference); + var configService = workspace.ExportProvider.GetExportedValue(); + configService.Options = new WorkspaceConfigurationOptions(SourceGeneratorExecution: preference); var solutionX = workspace.CurrentSolution; @@ -520,7 +520,12 @@ End Class var classDz = compilation2Z.SourceModule.GlobalNamespace.GetTypeMembers("D").Single(); var classCz = classDz.BaseType; - Assert.Equal(TypeKind.Error, classCz.TypeKind); + // In balanced mode the skeleton won't be regenerated. So the downstream project won't see the change to + // remove the class. + if (preference is SourceGeneratorExecutionPreference.Automatic) + Assert.Equal(TypeKind.Error, classCz.TypeKind); + else + Assert.Equal(TypeKind.Class, classCz.TypeKind); } [WpfFact] @@ -591,8 +596,8 @@ internal async Task TestGetCompilationOnCrossLanguageDependentProjectChangedInPr using var workspace = CreateWorkspace(disablePartialSolutions: false, composition: composition); - var options = workspace.ExportProvider.GetExportedValue(); - options.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, preference); + var configService = workspace.ExportProvider.GetExportedValue(); + configService.Options = new WorkspaceConfigurationOptions(SourceGeneratorExecution: preference); var trackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); var solutionX = workspace.CurrentSolution; @@ -675,8 +680,16 @@ End Class } } - // Should find now that we're going a normal compilation. - Assert.True(foundTheError, "Did not find error"); + // In balanced mode the skeleton won't be regenerated. So the downstream project won't see the change to + // remove the class. So it will not find the error symbol. + if (preference is SourceGeneratorExecutionPreference.Automatic) + { + Assert.True(foundTheError, "Did not find error"); + } + else + { + Assert.False(foundTheError); + } } [Fact] From c1a461d01fbef0790d94fecabf31fa58b733a051 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 13:58:55 -0700 Subject: [PATCH 13/28] Update tests --- .../CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index 5539aca51eb1..dab3f3ac8d0c 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs +++ b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs @@ -683,13 +683,9 @@ End Class // In balanced mode the skeleton won't be regenerated. So the downstream project won't see the change to // remove the class. So it will not find the error symbol. if (preference is SourceGeneratorExecutionPreference.Automatic) - { - Assert.True(foundTheError, "Did not find error"); - } + Assert.True(foundTheError); else - { Assert.False(foundTheError); - } } [Fact] From 09864fb172d64f20738c33d00d2c22c824321bfe Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 14:02:19 -0700 Subject: [PATCH 14/28] Update rename --- src/EditorFeatures/Test2/Rename/RenameEngineResult.vb | 9 ++++++--- src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb | 8 +++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index 3cfdffe90f3d..510962f02c6f 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -55,7 +55,8 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename host As RenameTestHost, Optional renameOptions As SymbolRenameOptions = Nothing, Optional expectFailure As Boolean = False, - Optional sourceGenerator As ISourceGenerator = Nothing) As RenameEngineResult + Optional sourceGenerator As ISourceGenerator = Nothing, + Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Balanced) As RenameEngineResult Dim composition = EditorTestCompositions.EditorFeatures.AddParts( GetType(NoCompilationContentTypeLanguageService), @@ -68,9 +69,11 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename End If Dim workspace = TestWorkspace.CreateWorkspace(workspaceXml, composition:=composition) + + Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) + configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=executionPreference) + workspace.Services.SolutionServices.SetWorkspaceTestOutput(helper) - workspace.GlobalOptions.SetGlobalOption( - WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic) If sourceGenerator IsNot Nothing Then workspace.OnAnalyzerReferenceAdded(workspace.CurrentSolution.ProjectIds.Single(), New TestGeneratorReference(sourceGenerator)) diff --git a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb index e406a633b70e..dad3866b291f 100644 --- a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb @@ -548,8 +548,8 @@ class D : B Optional renameFile As Boolean = False, Optional resolvableConflictText As String = Nothing, Optional unresolvableConflictText As String = Nothing, - Optional severity As RenameDashboardSeverity = RenameDashboardSeverity.None - ) As Tasks.Task + Optional severity As RenameDashboardSeverity = RenameDashboardSeverity.None, + Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Balanced) As Task Using workspace = CreateWorkspaceWithWaiter(test, host) Dim globalOptions = workspace.GetService(Of IGlobalOptionService)() @@ -557,7 +557,9 @@ class D : B globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInStrings, renameInStrings) globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameInComments, renameInComments) globalOptions.SetGlobalOption(InlineRenameSessionOptionsStorage.RenameFile, renameFile) - globalOptions.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic) + + Dim configService = workspace.ExportProvider.GetExportedValue(Of TestWorkspaceConfigurationService) + configService.Options = New WorkspaceConfigurationOptions(SourceGeneratorExecution:=executionPreference) Dim cursorDocument = workspace.Documents.Single(Function(d) d.CursorPosition.HasValue) Dim cursorPosition = cursorDocument.CursorPosition.Value From d4acc8247405d26bdd100fd1a8d4cc6ed857991e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 14:06:30 -0700 Subject: [PATCH 15/28] Automatic by default --- src/EditorFeatures/Test2/Rename/RenameEngineResult.vb | 2 +- src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb index 510962f02c6f..f0aa89b965ad 100644 --- a/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb +++ b/src/EditorFeatures/Test2/Rename/RenameEngineResult.vb @@ -56,7 +56,7 @@ Namespace Microsoft.CodeAnalysis.Editor.UnitTests.Rename Optional renameOptions As SymbolRenameOptions = Nothing, Optional expectFailure As Boolean = False, Optional sourceGenerator As ISourceGenerator = Nothing, - Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Balanced) As RenameEngineResult + Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Automatic) As RenameEngineResult Dim composition = EditorTestCompositions.EditorFeatures.AddParts( GetType(NoCompilationContentTypeLanguageService), diff --git a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb index dad3866b291f..19ecabcdd2cd 100644 --- a/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb +++ b/src/EditorFeatures/Test2/Rename/RenameViewModelTests.vb @@ -549,7 +549,7 @@ class D : B Optional resolvableConflictText As String = Nothing, Optional unresolvableConflictText As String = Nothing, Optional severity As RenameDashboardSeverity = RenameDashboardSeverity.None, - Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Balanced) As Task + Optional executionPreference As SourceGeneratorExecutionPreference = SourceGeneratorExecutionPreference.Automatic) As Task Using workspace = CreateWorkspaceWithWaiter(test, host) Dim globalOptions = workspace.GetService(Of IGlobalOptionService)() From bc793cf7d7dd6d84cd566dbc438bcd54ed36fa4b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 14:25:48 -0700 Subject: [PATCH 16/28] Automatic for LSP --- .../Microsoft.CodeAnalysis.LanguageServer/Program.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs index cdd36c6cb052..349adc7ecaec 100644 --- a/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs +++ b/src/Features/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs @@ -10,6 +10,7 @@ using System.Runtime.Loader; using System.Text.Json; using Microsoft.CodeAnalysis.Contracts.Telemetry; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.LanguageServer.BrokeredServices; using Microsoft.CodeAnalysis.LanguageServer.HostWorkspace; @@ -17,6 +18,7 @@ using Microsoft.CodeAnalysis.LanguageServer.Logging; using Microsoft.CodeAnalysis.LanguageServer.Services; using Microsoft.CodeAnalysis.LanguageServer.StarredSuggestions; +using Microsoft.CodeAnalysis.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; using Roslyn.Utilities; @@ -82,6 +84,11 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation using var exportProvider = await ExportProviderBuilder.CreateExportProviderAsync(extensionManager, serverConfiguration.DevKitDependencyPath, loggerFactory); + // LSP server doesn't have the pieces yet to support 'balanced' mode for source-generators. Hardcode us to + // 'automatic' for now. + var globalOptionService = exportProvider.GetExportedValue(); + globalOptionService.SetGlobalOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic); + // The log file directory passed to us by VSCode might not exist yet, though its parent directory is guaranteed to exist. Directory.CreateDirectory(serverConfiguration.ExtensionLogDirectory); From 0e2d87e0158264a842a9f0a3927cf022c80d378e Mon Sep 17 00:00:00 2001 From: David Wengier Date: Wed, 22 May 2024 08:05:51 +1000 Subject: [PATCH 17/28] PR Feedback etc. --- .../Protocol/Extensions/ProtocolConversions.cs | 2 +- .../Converters/NaturalObjectConverter.cs | 6 ++++-- .../Razor/Cohost/Handlers/FoldingRanges.cs | 17 ++++++++--------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs index 9b18f67090ed..2f00c118bca7 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/ProtocolConversions.cs @@ -110,7 +110,7 @@ internal static partial class ProtocolConversions public static JsonSerializerOptions AddLspSerializerOptions(this JsonSerializerOptions options) { LSP.VSInternalExtensionUtilities.AddVSInternalExtensionConverters(options); - options.Converters.Add(new NaturalObjectConverter()); + options.Converters.Add(new LSP.NaturalObjectConverter()); return options; } diff --git a/src/Features/LanguageServer/Protocol/Protocol/Converters/NaturalObjectConverter.cs b/src/Features/LanguageServer/Protocol/Protocol/Converters/NaturalObjectConverter.cs index c69b5f8bfbeb..727c163990c3 100644 --- a/src/Features/LanguageServer/Protocol/Protocol/Converters/NaturalObjectConverter.cs +++ b/src/Features/LanguageServer/Protocol/Protocol/Converters/NaturalObjectConverter.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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. @@ -7,6 +7,8 @@ using System.Text.Json; using System.Text.Json.Serialization; +namespace Roslyn.LanguageServer.Protocol; + // copied from https://github.com/dotnet/runtime/issues/98038 to match newtonsoft behavior internal class NaturalObjectConverter : JsonConverter { @@ -68,4 +70,4 @@ public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOp throw new JsonException(); } } -} \ No newline at end of file +} diff --git a/src/Tools/ExternalAccess/Razor/Cohost/Handlers/FoldingRanges.cs b/src/Tools/ExternalAccess/Razor/Cohost/Handlers/FoldingRanges.cs index 200e40c7eccd..33237859a59e 100644 --- a/src/Tools/ExternalAccess/Razor/Cohost/Handlers/FoldingRanges.cs +++ b/src/Tools/ExternalAccess/Razor/Cohost/Handlers/FoldingRanges.cs @@ -9,17 +9,16 @@ using Microsoft.CodeAnalysis.Options; using Roslyn.LanguageServer.Protocol; -namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers +namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost.Handlers; + +internal static class FoldingRanges { - internal static class FoldingRanges + public static Task GetFoldingRangesAsync(Document document, CancellationToken cancellationToken) { - public static Task GetFoldingRangesAsync(Document document, CancellationToken cancellationToken) - { - // We need to manually get the IGlobalOptionsService out of the Mef composition, because Razor has its own - // composition so can't import it (and its internal anyway) - var globalOptions = document.Project.Solution.Services.ExportProvider.GetExports().First().Value; + // We need to manually get the IGlobalOptionsService out of the Mef composition, because Razor has its own + // composition so can't import it (and its internal anyway) + var globalOptions = document.Project.Solution.Services.ExportProvider.GetExports().First().Value; - return FoldingRangesHandler.GetFoldingRangesAsync(globalOptions, document, cancellationToken); - } + return FoldingRangesHandler.GetFoldingRangesAsync(globalOptions, document, cancellationToken); } } From f3c2f0cdb741890b7aabba045b17fe3f27604f84 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 16:22:19 -0700 Subject: [PATCH 18/28] Rerun on analyzer added --- .../Workspace/ProjectSystem/ProjectSystemProject.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index 4f387f594bed..bffcc1de1673 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -536,6 +536,8 @@ private async Task OnBatchScopeDisposedMaybeAsync(bool useAsync) var additionalDocumentsToOpen = new List<(DocumentId documentId, SourceTextContainer textContainer)>(); var analyzerConfigDocumentsToOpen = new List<(DocumentId documentId, SourceTextContainer textContainer)>(); + var hasAnalyzerChanges = _analyzersAddedInBatch.Count > 0 || _analyzersRemovedInBatch.Count > 0; + await _projectSystemProjectFactory.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges => { _sourceFiles.UpdateSolutionForBatch( @@ -686,6 +688,10 @@ await _projectSystemProjectFactory.ApplyBatchChangeToWorkspaceMaybeAsync(useAsyn { await _projectSystemProjectFactory.RaiseOnDocumentsAddedMaybeAsync(useAsync, documentFileNamesAdded.ToImmutable()).ConfigureAwait(false); } + + // If we added or removed analyzers, then re-run all generators to bring them up to date. + if (hasAnalyzerChanges) + _projectSystemProjectFactory.Workspace.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: true); } } From 8f01700e86b38c45b0de7728497ccb9be8c4578f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 16:26:06 -0700 Subject: [PATCH 19/28] formatting --- .../Workspace/ProjectSystem/ProjectSystemProject.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index bffcc1de1673..17d034fbf095 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -669,25 +669,17 @@ await _projectSystemProjectFactory.ApplyBatchChangeToWorkspaceMaybeAsync(useAsyn }).ConfigureAwait(false); foreach (var (documentId, textContainer) in documentsToOpen) - { await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnDocumentOpened(documentId, textContainer)).ConfigureAwait(false); - } foreach (var (documentId, textContainer) in additionalDocumentsToOpen) - { await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAdditionalDocumentOpened(documentId, textContainer)).ConfigureAwait(false); - } foreach (var (documentId, textContainer) in analyzerConfigDocumentsToOpen) - { await _projectSystemProjectFactory.ApplyChangeToWorkspaceMaybeAsync(useAsync, w => w.OnAnalyzerConfigDocumentOpened(documentId, textContainer)).ConfigureAwait(false); - } // Give the host the opportunity to check if those files are open if (documentFileNamesAdded.Count > 0) - { await _projectSystemProjectFactory.RaiseOnDocumentsAddedMaybeAsync(useAsync, documentFileNamesAdded.ToImmutable()).ConfigureAwait(false); - } // If we added or removed analyzers, then re-run all generators to bring them up to date. if (hasAnalyzerChanges) From d94e4d73bf718283b60afe3f7c6c70f6a17ad45a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 17:30:21 -0700 Subject: [PATCH 20/28] Search less tokens while finding constructors --- .../FindReferences/FindReferenceCache.cs | 30 ++++++++++---- .../ConstructorSymbolReferenceFinder.cs | 40 ++++++++++++------- .../Services/SyntaxFacts/CSharpSyntaxKinds.cs | 3 +- .../Core/Services/SyntaxFacts/ISyntaxKinds.cs | 7 ++-- .../SyntaxFacts/VisualBasicSyntaxKinds.vb | 3 +- 5 files changed, 56 insertions(+), 27 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index 7fec0716af60..9793905be741 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -130,11 +130,25 @@ public ImmutableArray FindMatchingIdentifierTokens( // walks the root and checks all identifier tokens. // // otherwise, we can use the text of the document to quickly find candidates and test those directly. - return this.SyntaxTreeIndex.ProbablyContainsEscapedIdentifier(identifier) - ? _identifierCache.GetOrAdd(identifier, identifier => FindMatchingIdentifierTokensFromTree(identifier, cancellationToken)) - : _identifierCache.GetOrAdd(identifier, _ => FindMatchingIdentifierTokensFromText(identifier, cancellationToken)); + if (this.SyntaxTreeIndex.ProbablyContainsEscapedIdentifier(identifier)) + { + return _identifierCache.GetOrAdd( + identifier, + identifier => FindMatchingIdentifierTokensFromTree(identifier, cancellationToken)); + } + + return _identifierCache.GetOrAdd( + identifier, + identifier => FindMatchingIdentifierTokensFromText( + identifier, + static (identifier, token, @this) => @this.IsMatch(identifier, token), + this, cancellationToken)); } + public ImmutableArray FindMatchingTextTokens( + string text, Func isMatch, TArgs args, CancellationToken cancellationToken) + => _identifierCache.GetOrAdd(text, _ => FindMatchingIdentifierTokensFromText(text, isMatch, args, cancellationToken)); + private bool IsMatch(string identifier, SyntaxToken token) => !token.IsMissing && this.SyntaxFacts.IsIdentifier(token) && this.SyntaxFacts.TextMatch(token.ValueText, identifier); @@ -176,22 +190,22 @@ private ImmutableArray FindMatchingIdentifierTokensFromTree( return result.ToImmutableAndClear(); } - private ImmutableArray FindMatchingIdentifierTokensFromText( - string identifier, CancellationToken cancellationToken) + private ImmutableArray FindMatchingIdentifierTokensFromText( + string text, Func isMatch, TArgs args, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var result); var index = 0; - while ((index = this.Text.IndexOf(identifier, index, this.SyntaxFacts.IsCaseSensitive)) >= 0) + while ((index = this.Text.IndexOf(text, index, this.SyntaxFacts.IsCaseSensitive)) >= 0) { cancellationToken.ThrowIfCancellationRequested(); var token = this.Root.FindToken(index, findInsideTrivia: true); var span = token.Span; - if (span.Start == index && span.Length == identifier.Length && IsMatch(identifier, token)) + if (span.Start == index && span.Length == text.Length && isMatch(text, token, args)) result.Add(token); - var nextIndex = index + identifier.Length; + var nextIndex = index + text.Length; nextIndex = Math.Max(nextIndex, token.SpanStart); index = nextIndex; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 19c6c488915a..3c76b1548075 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -190,13 +190,28 @@ private static void FindAttributeReferences( FindReferencesInDocumentUsingIdentifier(symbol, simpleName, state, processResult, processResultData, cancellationToken); } - private void FindReferencesInImplicitObjectCreationExpression( + private static void FindReferencesInImplicitObjectCreationExpression( IMethodSymbol symbol, FindReferencesDocumentState state, Action processResult, TData processResultData, CancellationToken cancellationToken) { + if (!state.Cache.SyntaxTreeIndex.ContainsImplicitObjectCreation) + return; + + // Note: only C# supports implicit object creation. So we don't need to do anything special around case + // insensitive matching here. + // + // Search for all the `new` tokens in the file. + var newKeywordTokens = state.Cache.FindMatchingTextTokens( + "new", + (_, token, syntaxKinds) => token.RawKind == syntaxKinds.NewKeyword, + state.SyntaxFacts.SyntaxKinds, + cancellationToken); + if (newKeywordTokens.IsEmpty) + return; + // Only check `new (...)` calls that supply enough arguments to match all the required parameters for the constructor. var minimumArgumentCount = symbol.Parameters.Count(p => !p.IsOptional && !p.IsParams); var maximumArgumentCount = symbol.Parameters is [.., { IsParams: true }] @@ -208,16 +223,12 @@ private void FindReferencesInImplicitObjectCreationExpression( : symbol.Parameters.Length; var implicitObjectKind = state.SyntaxFacts.SyntaxKinds.ImplicitObjectCreationExpression; - FindReferencesInDocument(state, IsRelevantDocument, CollectMatchingReferences, processResult, processResultData, cancellationToken); - return; - - static bool IsRelevantDocument(SyntaxTreeIndex syntaxTreeInfo) - => syntaxTreeInfo.ContainsImplicitObjectCreation; - - void CollectMatchingReferences( - SyntaxNode node, FindReferencesDocumentState state, Action processResult, TData processResultData) + foreach (var newKeywordToken in newKeywordTokens) { - if (node.RawKind != implicitObjectKind) + cancellationToken.ThrowIfCancellationRequested(); + + var node = newKeywordToken.Parent; + if (node is null || node.RawKind != implicitObjectKind) return; // if there are too few or too many arguments, then don't bother checking. @@ -232,11 +243,12 @@ void CollectMatchingReferences( var constructor = state.SemanticModel.GetSymbolInfo(node, cancellationToken).Symbol; if (Matches(constructor, symbol)) { - var location = node.GetFirstToken().GetLocation(); - var symbolUsageInfo = GetSymbolUsageInfo(node, state, cancellationToken); - var result = new FinderLocation(node, new ReferenceLocation( - state.Document, alias: null, location, isImplicit: true, symbolUsageInfo, + state.Document, + alias: null, + newKeywordToken.GetLocation(), + isImplicit: true, + GetSymbolUsageInfo(node, state, cancellationToken), GetAdditionalFindUsagesProperties(node, state), CandidateReason.None)); processResult(result, processResultData); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs index c5e112ab1bc2..3aefcfa76563 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs @@ -65,9 +65,10 @@ public int Convert(TSyntaxKind kind) where TSyntaxKind : struct public int XmlTextLiteralToken => (int)SyntaxKind.XmlTextLiteralToken; public int DelegateKeyword => (int)SyntaxKind.DelegateKeyword; + public int FalseKeyword => (int)SyntaxKind.FalseKeyword; public int IfKeyword => (int)SyntaxKind.IfKeyword; + public int NewKeyword => (int)SyntaxKind.NewKeyword; public int TrueKeyword => (int)SyntaxKind.TrueKeyword; - public int FalseKeyword => (int)SyntaxKind.FalseKeyword; public int UsingKeyword => (int)SyntaxKind.UsingKeyword; public int GenericName => (int)SyntaxKind.GenericName; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs index 4d304e6a3f4a..ddaf52a46398 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs @@ -46,14 +46,15 @@ internal interface ISyntaxKinds #region keywords - int AwaitKeyword { get; } int AsyncKeyword { get; } + int AwaitKeyword { get; } int DelegateKeyword { get; } + int FalseKeyword { get; } int GlobalKeyword { get; } - int IfKeyword { get; } int? GlobalStatement { get; } + int IfKeyword { get; } + int NewKeyword { get; } int TrueKeyword { get; } - int FalseKeyword { get; } int UsingKeyword { get; } #endregion diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb index 24f77d43ecd1..b6de084b165a 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb @@ -67,9 +67,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageService Public ReadOnly Property XmlTextLiteralToken As Integer = SyntaxKind.XmlTextLiteralToken Implements ISyntaxKinds.XmlTextLiteralToken Public ReadOnly Property DelegateKeyword As Integer = SyntaxKind.DelegateKeyword Implements ISyntaxKinds.DelegateKeyword + Public ReadOnly Property FalseKeyword As Integer = SyntaxKind.FalseKeyword Implements ISyntaxKinds.FalseKeyword Public ReadOnly Property IfKeyword As Integer = SyntaxKind.IfKeyword Implements ISyntaxKinds.IfKeyword + Public ReadOnly Property NewKeyword As Integer = SyntaxKind.NewKeyword Implements ISyntaxKinds.NewKeyword Public ReadOnly Property TrueKeyword As Integer = SyntaxKind.TrueKeyword Implements ISyntaxKinds.TrueKeyword - Public ReadOnly Property FalseKeyword As Integer = SyntaxKind.FalseKeyword Implements ISyntaxKinds.FalseKeyword Public ReadOnly Property UsingKeyword As Integer = SyntaxKind.UsingKeyword Implements ISyntaxKinds.UsingKeyword Public ReadOnly Property GenericName As Integer = SyntaxKind.GenericName Implements ISyntaxKinds.GenericName From 45cb0c8925419be714ac05cc463a1772bc7ed695 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 21 May 2024 17:34:56 -0700 Subject: [PATCH 21/28] Add extract method support for ref struct interfaces (#73619) * Add extract method support for ref struct interfaces The method extraction code uses ITypeParameterSymbol's constraints to generate the new method's text. This codepath had not yet been modified to support ITypeParameterSymbol.AllowsRefLikeType This is in support of the "allows ref struct" on interfaces feature outlined here: https://github.com/dotnet/roslyn/issues/72124 This ref structs for interfaces feature was merged via this PR: https://github.com/dotnet/roslyn/pull/73567 --- .../ExtractMethodTests.LanguageInteraction.cs | 33 +++++++++++++++++++ .../MethodExtractor.CodeGenerator.cs | 2 +- .../Extensions/IMethodSymbolExtensions.cs | 1 + .../ITypeParameterSymbolExtensions.cs | 6 ++++ .../CodeGenerationSymbolFactory.cs | 6 ++-- .../CodeGenerationTypeParameterSymbol.cs | 6 ++-- 6 files changed, 49 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.cs b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.cs index 3110cdb80940..677ff9c6ecc6 100644 --- a/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.cs +++ b/src/EditorFeatures/CSharpTest/ExtractMethod/ExtractMethodTests.LanguageInteraction.cs @@ -58,6 +58,39 @@ class Program await TestExtractMethodAsync(code, expected); } + [Fact] + public async Task SelectTypeParameterWithAllowsRefStructAntiConstraint() + { + var code = """ + using System; + + class Program + { + void MyMethod1(TT tt) where TT : IDisposable, allows ref struct + { + [|tt.Dispose();|] + } + } + """; + var expected = """ + using System; + + class Program + { + void MyMethod1(TT tt) where TT : IDisposable, allows ref struct + { + NewMethod(tt); + } + + private static void NewMethod(TT tt) where TT : IDisposable, allows ref struct + { + tt.Dispose(); + } + } + """; + + await TestExtractMethodAsync(code, expected); + } [Fact] public async Task SelectTypeParameter() { diff --git a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs index 609df5968886..10ad94682936 100644 --- a/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs +++ b/src/Features/Core/Portable/ExtractMethod/MethodExtractor.CodeGenerator.cs @@ -344,7 +344,7 @@ protected ImmutableArray CreateMethodTypeParameters() typeParameters.Add(CodeGenerationSymbolFactory.CreateTypeParameter( parameter.GetAttributes(), parameter.Variance, parameter.Name, [], parameter.NullableAnnotation, parameter.HasConstructorConstraint, parameter.HasReferenceTypeConstraint, parameter.HasUnmanagedTypeConstraint, - parameter.HasValueTypeConstraint, parameter.HasNotNullConstraint, parameter.Ordinal)); + parameter.HasValueTypeConstraint, parameter.HasNotNullConstraint, parameter.AllowsRefLikeType, parameter.Ordinal)); } return typeParameters.ToImmutableAndFree(); diff --git a/src/Workspaces/Core/Portable/Shared/Extensions/IMethodSymbolExtensions.cs b/src/Workspaces/Core/Portable/Shared/Extensions/IMethodSymbolExtensions.cs index 8c9586710b33..1cfc95216675 100644 --- a/src/Workspaces/Core/Portable/Shared/Extensions/IMethodSymbolExtensions.cs +++ b/src/Workspaces/Core/Portable/Shared/Extensions/IMethodSymbolExtensions.cs @@ -137,6 +137,7 @@ private static ImmutableArray RenameTypeParameters( typeParameter.HasValueTypeConstraint, typeParameter.HasUnmanagedTypeConstraint, typeParameter.HasNotNullConstraint, + typeParameter.AllowsRefLikeType, typeParameter.Ordinal); newTypeParameters.Add(newTypeParameter); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeParameterSymbolExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeParameterSymbolExtensions.cs index 27e830021453..70ae62d93d8b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeParameterSymbolExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Extensions/ITypeParameterSymbolExtensions.cs @@ -74,6 +74,12 @@ private static void AddConstraintClauses( constraints.Add(ConstructorConstraint()); } + if (typeParameter.AllowsRefLikeType) + { + // "allows ref struct" anti-constraint must be last + constraints.Add(AllowsConstraintClause([RefStructConstraint()])); + } + if (constraints.Count == 0) { return; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationSymbolFactory.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationSymbolFactory.cs index 32456f90c076..c0e38b5abedb 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationSymbolFactory.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/CodeGenerationSymbolFactory.cs @@ -322,7 +322,8 @@ public static ITypeParameterSymbol CreateTypeParameterSymbol(string name, int or attributes: default, varianceKind: VarianceKind.None, name: name, constraintTypes: [], hasConstructorConstraint: false, hasReferenceConstraint: false, hasValueConstraint: false, - hasUnmanagedConstraint: false, hasNotNullConstraint: false, ordinal: ordinal); + hasUnmanagedConstraint: false, hasNotNullConstraint: false, allowsRefLikeType: false, + ordinal: ordinal); } /// @@ -338,9 +339,10 @@ public static ITypeParameterSymbol CreateTypeParameter( bool hasUnmanagedConstraint = false, bool hasValueConstraint = false, bool hasNotNullConstraint = false, + bool allowsRefLikeType = false, int ordinal = 0) { - return new CodeGenerationTypeParameterSymbol(null, attributes, varianceKind, name, nullableAnnotation, constraintTypes, hasConstructorConstraint, hasReferenceConstraint, hasValueConstraint, hasUnmanagedConstraint, hasNotNullConstraint, ordinal); + return new CodeGenerationTypeParameterSymbol(null, attributes, varianceKind, name, nullableAnnotation, constraintTypes, hasConstructorConstraint, hasReferenceConstraint, hasValueConstraint, hasUnmanagedConstraint, hasNotNullConstraint, allowsRefLikeType, ordinal); } /// diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationTypeParameterSymbol.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationTypeParameterSymbol.cs index 674fc9745996..b97e71d3c3ac 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationTypeParameterSymbol.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/CodeGeneration/Symbols/CodeGenerationTypeParameterSymbol.cs @@ -20,6 +20,7 @@ internal class CodeGenerationTypeParameterSymbol( bool hasValueConstraint, bool hasUnmanagedConstraint, bool hasNotNullConstraint, + bool allowsRefLikeType, int ordinal) : CodeGenerationTypeSymbol(containingType?.ContainingAssembly, containingType, attributes, Accessibility.NotApplicable, default, name, SpecialType.None, nullableAnnotation), ITypeParameterSymbol { public VarianceKind Variance { get; } = varianceKind; @@ -27,7 +28,7 @@ internal class CodeGenerationTypeParameterSymbol( public bool HasConstructorConstraint { get; } = hasConstructorConstraint; public bool HasReferenceTypeConstraint { get; } = hasReferenceConstraint; public bool HasValueTypeConstraint { get; } = hasValueConstraint; - public bool AllowsRefLikeType => false; + public bool AllowsRefLikeType { get; } = allowsRefLikeType; public bool HasUnmanagedTypeConstraint { get; } = hasUnmanagedConstraint; public bool HasNotNullConstraint { get; } = hasNotNullConstraint; public int Ordinal { get; } = ordinal; @@ -37,7 +38,8 @@ protected override CodeGenerationTypeSymbol CloneWithNullableAnnotation(Nullable return new CodeGenerationTypeParameterSymbol( this.ContainingType, this.GetAttributes(), this.Variance, this.Name, nullableAnnotation, this.ConstraintTypes, this.HasConstructorConstraint, this.HasReferenceTypeConstraint, - this.HasValueTypeConstraint, this.HasUnmanagedTypeConstraint, this.HasNotNullConstraint, this.Ordinal); + this.HasValueTypeConstraint, this.HasUnmanagedTypeConstraint, this.HasNotNullConstraint, + this.AllowsRefLikeType, this.Ordinal); } public new ITypeParameterSymbol OriginalDefinition => this; From b8b677619172bc42c611a5d78bf7fa66db3fd851 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 17:37:26 -0700 Subject: [PATCH 22/28] Search less tokens while finding constructors --- .../FindReferences/FindReferenceCache.cs | 55 +++++++++++-------- ...tructorInitializerSymbolReferenceFinder.cs | 2 +- .../ConstructorSymbolReferenceFinder.cs | 6 +- 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index 9793905be741..f76b366efde4 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -68,6 +68,7 @@ static async Task ComputeCacheAsync(Document document, Cance private ImmutableHashSet? _aliasNameSet; private ImmutableArray _constructorInitializerCache; + private ImmutableArray _newKeywordsCache; private FindReferenceCache( Document document, SourceText text, SemanticModel semanticModel, SemanticModel nullableEnabledSemanticModel, SyntaxNode root, SyntaxTreeIndex syntaxTreeIndex) @@ -139,16 +140,12 @@ public ImmutableArray FindMatchingIdentifierTokens( return _identifierCache.GetOrAdd( identifier, - identifier => FindMatchingIdentifierTokensFromText( + identifier => FindMatchingTokensFromText( identifier, static (identifier, token, @this) => @this.IsMatch(identifier, token), this, cancellationToken)); } - public ImmutableArray FindMatchingTextTokens( - string text, Func isMatch, TArgs args, CancellationToken cancellationToken) - => _identifierCache.GetOrAdd(text, _ => FindMatchingIdentifierTokensFromText(text, isMatch, args, cancellationToken)); - private bool IsMatch(string identifier, SyntaxToken token) => !token.IsMissing && this.SyntaxFacts.IsIdentifier(token) && this.SyntaxFacts.TextMatch(token.ValueText, identifier); @@ -190,7 +187,7 @@ private ImmutableArray FindMatchingIdentifierTokensFromTree( return result.ToImmutableAndClear(); } - private ImmutableArray FindMatchingIdentifierTokensFromText( + private ImmutableArray FindMatchingTokensFromText( string text, Func isMatch, TArgs args, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var result); @@ -212,33 +209,47 @@ private ImmutableArray FindMatchingIdentifierTokensFromText( return result.ToImmutableAndClear(); } - public IEnumerable GetConstructorInitializerTokens( - ISyntaxFactsService syntaxFacts, SyntaxNode root, CancellationToken cancellationToken) + + public ImmutableArray GetConstructorInitializerTokens(CancellationToken cancellationToken) { // this one will only get called when we know given document contains constructor initializer. // no reason to use text to check whether it exist first. if (_constructorInitializerCache.IsDefault) - { - var initializers = GetConstructorInitializerTokensWorker(syntaxFacts, root, cancellationToken); - ImmutableInterlocked.InterlockedInitialize(ref _constructorInitializerCache, initializers); - } + ImmutableInterlocked.InterlockedInitialize(ref _constructorInitializerCache, GetConstructorInitializerTokensWorker()); return _constructorInitializerCache; - } - private static ImmutableArray GetConstructorInitializerTokensWorker( - ISyntaxFactsService syntaxFacts, SyntaxNode root, CancellationToken cancellationToken) - { - using var _ = ArrayBuilder.GetInstance(out var initializers); - foreach (var constructor in syntaxFacts.GetConstructors(root, cancellationToken)) + ImmutableArray GetConstructorInitializerTokensWorker() { - foreach (var token in constructor.DescendantTokens(descendIntoTrivia: false)) + var syntaxFacts = this.SyntaxFacts; + using var _ = ArrayBuilder.GetInstance(out var initializers); + foreach (var constructor in syntaxFacts.GetConstructors(this.Root, cancellationToken)) { - if (syntaxFacts.IsThisConstructorInitializer(token) || syntaxFacts.IsBaseConstructorInitializer(token)) - initializers.Add(token); + foreach (var token in constructor.DescendantTokens(descendIntoTrivia: false)) + { + if (syntaxFacts.IsThisConstructorInitializer(token) || syntaxFacts.IsBaseConstructorInitializer(token)) + initializers.Add(token); + } } + + return initializers.ToImmutableAndClear(); } + } + + public ImmutableArray GetNewKeywordTokens(CancellationToken cancellationToken) + { + if (_newKeywordsCache.IsDefault) + ImmutableInterlocked.InterlockedInitialize(ref _newKeywordsCache, GetNewKeywordTokensWorker()); + + return _newKeywordsCache; - return initializers.ToImmutableAndClear(); + ImmutableArray GetNewKeywordTokensWorker() + { + return this.FindMatchingTokensFromText( + this.SyntaxFacts.GetText(this.SyntaxFacts.SyntaxKinds.NewKeyword), + static (_, token, syntaxKinds) => token.RawKind == syntaxKinds.NewKeyword, + this.SyntaxFacts.SyntaxKinds, + cancellationToken); + } } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs index b15a9593de89..1fcfdd74b8f0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorInitializerSymbolReferenceFinder.cs @@ -59,7 +59,7 @@ protected sealed override void FindReferencesInDocument( FindReferencesSearchOptions options, CancellationToken cancellationToken) { - var tokens = state.Cache.GetConstructorInitializerTokens(state.SyntaxFacts, state.Root, cancellationToken); + var tokens = state.Cache.GetConstructorInitializerTokens(cancellationToken); if (state.SemanticModel.Language == LanguageNames.VisualBasic) tokens = tokens.Concat(FindMatchingIdentifierTokens(state, "New", cancellationToken)).Distinct(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 3c76b1548075..3a88fc96b8d1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -204,11 +204,7 @@ private static void FindReferencesInImplicitObjectCreationExpression( // insensitive matching here. // // Search for all the `new` tokens in the file. - var newKeywordTokens = state.Cache.FindMatchingTextTokens( - "new", - (_, token, syntaxKinds) => token.RawKind == syntaxKinds.NewKeyword, - state.SyntaxFacts.SyntaxKinds, - cancellationToken); + var newKeywordTokens = state.Cache.GetNewKeywordTokens(cancellationToken); if (newKeywordTokens.IsEmpty) return; From db2efaf2b7ec66e9e31b4e01c454728171797e42 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 17:54:16 -0700 Subject: [PATCH 23/28] Make static --- .../FindReferences/Finders/ConstructorSymbolReferenceFinder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 3a88fc96b8d1..224be8c2c842 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -209,7 +209,7 @@ private static void FindReferencesInImplicitObjectCreationExpression( return; // Only check `new (...)` calls that supply enough arguments to match all the required parameters for the constructor. - var minimumArgumentCount = symbol.Parameters.Count(p => !p.IsOptional && !p.IsParams); + var minimumArgumentCount = symbol.Parameters.Count(static p => !p.IsOptional && !p.IsParams); var maximumArgumentCount = symbol.Parameters is [.., { IsParams: true }] ? int.MaxValue : symbol.Parameters.Length; From f11d4ca60b52bc82699c77bf4c8795810c7c02bd Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 18:52:15 -0700 Subject: [PATCH 24/28] Continue not return --- .../FindReferencesTests.ConstructorSymbols.vb | 27 +++++++++++++++++++ .../ConstructorSymbolReferenceFinder.cs | 6 ++--- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ConstructorSymbols.vb b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ConstructorSymbols.vb index 145f379319bc..89cc1601bf89 100644 --- a/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ConstructorSymbols.vb +++ b/src/EditorFeatures/Test2/FindReferences/FindReferencesTests.ConstructorSymbols.vb @@ -872,6 +872,33 @@ class C Await TestAPIAndFeature(input, kind, host) End Function + + Public Async Function TestConstructor_ImplicitAndExplicitObjectCreation_Local(kind As TestKind, host As TestHost) As Task + Dim input = + + + +class D +{ + public {|Definition:$$D|}() { } +} + + +class C +{ + void M() + { + D d = [|new|](); + D d2 = new [|D|](); + D d3 = [|new|](); + } +} + + + + Await TestAPIAndFeature(input, kind, host) + End Function + Public Async Function TestConstructor_ImplicitObjectCreation_Local_WithArguments(kind As TestKind, host As TestHost) As Task Dim input = diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs index 224be8c2c842..a3724613a3df 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/Finders/ConstructorSymbolReferenceFinder.cs @@ -225,16 +225,16 @@ private static void FindReferencesInImplicitObjectCreationExpression( var node = newKeywordToken.Parent; if (node is null || node.RawKind != implicitObjectKind) - return; + continue; // if there are too few or too many arguments, then don't bother checking. var actualArgumentCount = state.SyntaxFacts.GetArgumentsOfObjectCreationExpression(node).Count; if (actualArgumentCount < minimumArgumentCount || actualArgumentCount > maximumArgumentCount) - return; + continue; // if we need an exact count then make sure that the count we have fits the count we need. if (exactArgumentCount != -1 && exactArgumentCount != actualArgumentCount) - return; + continue; var constructor = state.SemanticModel.GetSymbolInfo(node, cancellationToken).Symbol; if (Matches(constructor, symbol)) From ac301e22853c9083a7aa8f22d49e389056da89de Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 21 May 2024 20:20:59 -0700 Subject: [PATCH 25/28] Add signature help support for ref struct interfaces (#73624) The selected display part building code for method constraints in signature help had not yet been modified to support ITypeParameterSymbol.AllowsRefLikeType This is in support of the "allows ref struct" on interfaces feature outlined here: #72124 This ref structs for interfaces feature was merged via this PR: #73567 --- .../GenericNameSignatureHelpProviderTests.cs | 24 +++++++++++++++++++ .../GenericNameSignatureHelpProvider.cs | 19 ++++++++++++++- .../Core/Utilities/SignatureComparer.cs | 3 ++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameSignatureHelpProviderTests.cs b/src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameSignatureHelpProviderTests.cs index 92428da8e2ee..3ac484a1a2ed 100644 --- a/src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameSignatureHelpProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/SignatureHelp/GenericNameSignatureHelpProviderTests.cs @@ -451,6 +451,30 @@ void Goo() await TestAsync(markup, expectedOrderedItems); } + [Fact] + public async Task DeclaringGenericTypeWithConstraintsAllowRefStruct() + { + var markup = """ + class G where S : allows ref struct + { }; + + class C + { + void Goo() + { + [|G<$$|]> + } + } + """; + + var expectedOrderedItems = new List + { + new SignatureHelpTestItem("G where S : allows ref struct", string.Empty, string.Empty, currentParameterIndex: 0) + }; + + await TestAsync(markup, expectedOrderedItems); + } + #endregion #region "Generic member invocation" diff --git a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider.cs b/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider.cs index 262c0ecb9216..0f1178ebb16d 100644 --- a/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider.cs +++ b/src/Features/CSharp/Portable/SignatureHelp/GenericNameSignatureHelpProvider.cs @@ -284,6 +284,22 @@ private static IList GetSelectedDisplayParts( parts.Add(Keyword(SyntaxKind.NewKeyword)); parts.Add(Punctuation(SyntaxKind.OpenParenToken)); parts.Add(Punctuation(SyntaxKind.CloseParenToken)); + needComma = true; + } + + if (typeParam.AllowsRefLikeType) + { + if (needComma) + { + parts.Add(Punctuation(SyntaxKind.CommaToken)); + parts.Add(Space()); + } + + parts.Add(Keyword(SyntaxKind.AllowsKeyword)); + parts.Add(Space()); + parts.Add(Keyword(SyntaxKind.RefKeyword)); + parts.Add(Space()); + parts.Add(Keyword(SyntaxKind.StructKeyword)); } } @@ -293,6 +309,7 @@ private static IList GetSelectedDisplayParts( private static bool TypeParameterHasConstraints(ITypeParameterSymbol typeParam) { return !typeParam.ConstraintTypes.IsDefaultOrEmpty || typeParam.HasConstructorConstraint || - typeParam.HasReferenceTypeConstraint || typeParam.HasValueTypeConstraint; + typeParam.HasReferenceTypeConstraint || typeParam.HasValueTypeConstraint || + typeParam.AllowsRefLikeType; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SignatureComparer.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SignatureComparer.cs index 0d7eb47beb28..9de17cde3aad 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SignatureComparer.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SignatureComparer.cs @@ -223,7 +223,8 @@ private bool HaveSameConstraints(ITypeParameterSymbol typeParameter1, ITypeParam { if (typeParameter1.HasConstructorConstraint != typeParameter2.HasConstructorConstraint || typeParameter1.HasReferenceTypeConstraint != typeParameter2.HasReferenceTypeConstraint || - typeParameter1.HasValueTypeConstraint != typeParameter2.HasValueTypeConstraint) + typeParameter1.HasValueTypeConstraint != typeParameter2.HasValueTypeConstraint || + typeParameter1.AllowsRefLikeType != typeParameter2.AllowsRefLikeType) { return false; } From fd25ebc58b152191ee1173a02202f465009a6066 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 21:20:27 -0700 Subject: [PATCH 26/28] Skip flakey test --- .../IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs index a30f071de61e..83dea6287ae2 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs @@ -719,7 +719,7 @@ public class Class2 }", HangMitigatingCancellationToken); } - [IdeFact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1903953/")] + [IdeFact(Skip = "https://github.com/dotnet/roslyn/issues/73630"), WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1903953/")] public async Task VerifyRenameLinkedDocumentsAsync() { var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); From 37e781c368fdc584d5065c0b098efcb789fc00ce Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 21:29:21 -0700 Subject: [PATCH 27/28] Raw strings --- .../CSharp/CSharpRename.cs | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs index 83dea6287ae2..39c1831c6fe8 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs @@ -725,33 +725,34 @@ public async Task VerifyRenameLinkedDocumentsAsync() var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); globalOptions.SetGlobalOption(InlineRenameUIOptionsStorage.UseInlineAdornment, true); var projectName = "MultiTFMProject"; - await TestServices.SolutionExplorer.AddCustomProjectAsync(projectName, ".csproj", @" - - - Exe - net6.0-windows;net48 - enable - enable - preview - - -", HangMitigatingCancellationToken); - - var startCode = @" -public class TestClass -{ -} -"; + await TestServices.SolutionExplorer.AddCustomProjectAsync(projectName, ".csproj", """ + + + Exe + net6.0-windows;net48 + enable + enable + preview + + + """, HangMitigatingCancellationToken); + + var startCode = """ + public class TestClass + { + } + """; await TestServices.SolutionExplorer.AddFileAsync(projectName, "TestClass.cs", startCode, cancellationToken: HangMitigatingCancellationToken); - var referencedCode = @" -public class MyClass -{ - void Method() - { - TestClass x = new TestClass(); - } -}"; + var referencedCode = """ + public class MyClass + { + void Method() + { + TestClass x = new TestClass(); + } + } + """; await TestServices.SolutionExplorer.AddFileAsync(projectName, "MyClass.cs", referencedCode, cancellationToken: HangMitigatingCancellationToken); // We made csproj changes, so need to wait for PS to finish all the tasks before moving on. await TestServices.Workspace.WaitForProjectSystemAsync(HangMitigatingCancellationToken); @@ -763,14 +764,15 @@ void Method() await TestServices.Input.SendWithoutActivateAsync([VirtualKeyCode.HOME, "M", "y", VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); await TestServices.EditorVerifier.TextEqualsAsync( - @" -public class MyClass -{ - void Method() - { - MyTestClass$$ x = new MyTestClass(); - } -}", HangMitigatingCancellationToken); + """ + public class MyClass + { + void Method() + { + MyTestClass$$ x = new MyTestClass(); + } + } + """, HangMitigatingCancellationToken); // Make sure the file is renamed. If the file is not found, this call would throw exception await TestServices.SolutionExplorer.GetProjectItemAsync(projectName, "MyTestClass.cs", HangMitigatingCancellationToken); } From 281dfab6228e3e7405573f7dc5887ed87dd91a52 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 21 May 2024 21:45:41 -0700 Subject: [PATCH 28/28] Raw strings --- .../CSharp/CSharpRename.cs | 747 +++++++++--------- 1 file changed, 389 insertions(+), 358 deletions(-) diff --git a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs index 39c1831c6fe8..564e8c5dd959 100644 --- a/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs +++ b/src/VisualStudio/IntegrationTest/New.IntegrationTests/CSharp/CSharpRename.cs @@ -48,25 +48,26 @@ public override async Task InitializeAsync() [IdeFact] public async Task VerifyLocalVariableRename() { - var markup = @" -using System; -using System.Collections.Generic; -using System.Linq; + var markup = """ + using System; + using System.Collections.Generic; + using System.Linq; -class Program -{ - static void Main(string[] args) - { - int [|x|]$$ = 0; - [|x|] = 5; - TestMethod([|x|]); - } + class Program + { + static void Main(string[] args) + { + int [|x|]$$ = 0; + [|x|] = 5; + TestMethod([|x|]); + } - static void TestMethod(int y) - { + static void TestMethod(int y) + { - } -}"; + } + } + """; await using (var telemetry = await TestServices.Telemetry.EnableTestTelemetryChannelAsync(HangMitigatingCancellationToken)) { await SetUpEditorAsync(markup, HangMitigatingCancellationToken); @@ -79,25 +80,26 @@ static void TestMethod(int y) await TestServices.Input.SendWithoutActivateAsync([VirtualKeyCode.VK_Y, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -using System; -using System.Collections.Generic; -using System.Linq; + await TestServices.EditorVerifier.TextEqualsAsync(""" + using System; + using System.Collections.Generic; + using System.Linq; -class Program -{ - static void Main(string[] args) - { - int y$$ = 0; - y = 5; - TestMethod(y); - } + class Program + { + static void Main(string[] args) + { + int y$$ = 0; + y = 5; + TestMethod(y); + } - static void TestMethod(int y) - { + static void TestMethod(int y) + { - } -}", HangMitigatingCancellationToken); + } + } + """, HangMitigatingCancellationToken); await telemetry.VerifyFiredAsync(["vs/ide/vbcs/rename/inlinesession/session", "vs/ide/vbcs/rename/commitcore"], HangMitigatingCancellationToken); } } @@ -105,13 +107,13 @@ static void TestMethod(int y) [IdeFact, WorkItem("https://github.com/dotnet/roslyn/issues/21657")] public async Task VerifyAttributeRename() { - var markup = @" -using System; + var markup = """ + using System; -class [|$$ustom|]Attribute : Attribute -{ -} -"; + class [|$$ustom|]Attribute : Attribute + { + } + """; await SetUpEditorAsync(markup, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); @@ -122,25 +124,25 @@ class [|$$ustom|]Attribute : Attribute await TestServices.Input.SendWithoutActivateAsync(["Custom", VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -using System; + await TestServices.EditorVerifier.TextEqualsAsync(""" + using System; -class Custom$$Attribute : Attribute -{ -} -", HangMitigatingCancellationToken); + class Custom$$Attribute : Attribute + { + } + """, HangMitigatingCancellationToken); } [IdeFact, WorkItem("https://github.com/dotnet/roslyn/issues/21657")] public async Task VerifyAttributeRenameWhileRenameClasss() { - var markup = @" -using System; + var markup = """ + using System; -class [|$$stom|]Attribute : Attribute -{ -} -"; + class [|$$stom|]Attribute : Attribute + { + } + """; await SetUpEditorAsync(markup, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); @@ -151,30 +153,30 @@ class [|$$stom|]Attribute : Attribute await TestServices.Input.SendWithoutActivateAsync("Custom", HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -using System; + await TestServices.EditorVerifier.TextEqualsAsync(""" + using System; -class Custom$$Attribute : Attribute -{ -} -", HangMitigatingCancellationToken); + class Custom$$Attribute : Attribute + { + } + """, HangMitigatingCancellationToken); } [IdeFact, WorkItem("https://github.com/dotnet/roslyn/issues/21657")] public async Task VerifyAttributeRenameWhileRenameAttribute() { - var markup = @" -using System; + var markup = """ + using System; -[[|$$stom|]] -class Bar -{ -} + [[|$$stom|]] + class Bar + { + } -class [|stom|]Attribute : Attribute -{ -} -"; + class [|stom|]Attribute : Attribute + { + } + """; await SetUpEditorAsync(markup, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); @@ -185,35 +187,35 @@ class [|stom|]Attribute : Attribute await TestServices.Input.SendWithoutActivateAsync("Custom", HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -using System; + await TestServices.EditorVerifier.TextEqualsAsync(""" + using System; -[Custom$$] -class Bar -{ -} + [Custom$$] + class Bar + { + } -class CustomAttribute : Attribute -{ -} -", HangMitigatingCancellationToken); + class CustomAttribute : Attribute + { + } + """, HangMitigatingCancellationToken); } [IdeFact, WorkItem("https://github.com/dotnet/roslyn/issues/21657")] public async Task VerifyAttributeRenameWhileRenameAttributeClass() { - var markup = @" -using System; + var markup = """ + using System; -[[|stom|]] -class Bar -{ -} + [[|stom|]] + class Bar + { + } -class [|$$stom|]Attribute : Attribute -{ -} -"; + class [|$$stom|]Attribute : Attribute + { + } + """; await SetUpEditorAsync(markup, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); @@ -224,18 +226,18 @@ class [|$$stom|]Attribute : Attribute await TestServices.Input.SendWithoutActivateAsync("Custom", HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -using System; + await TestServices.EditorVerifier.TextEqualsAsync(""" + using System; -[Custom] -class Bar -{ -} + [Custom] + class Bar + { + } -class Custom$$Attribute : Attribute -{ -} -", HangMitigatingCancellationToken); + class Custom$$Attribute : Attribute + { + } + """, HangMitigatingCancellationToken); } [IdeFact] @@ -244,33 +246,34 @@ public async Task VerifyLocalVariableRenameWithCommentsUpdated() // "variable" is intentionally misspelled as "varixable" and "this" is misspelled as // "thix" below to ensure we don't change instances of "x" in comments that are part of // larger words - var markup = @" -using System; -using System.Collections.Generic; -using System.Linq; + var markup = """ + using System; + using System.Collections.Generic; + using System.Linq; -class Program -{ - /// - /// creates a varixable named [|x|] xx - /// - /// - static void Main(string[] args) - { - // thix varixable is named [|x|] xx - int [|x|]$$ = 0; - [|x|] = 5; - TestMethod([|x|]); - } + class Program + { + /// + /// creates a varixable named [|x|] xx + /// + /// + static void Main(string[] args) + { + // thix varixable is named [|x|] xx + int [|x|]$$ = 0; + [|x|] = 5; + TestMethod([|x|]); + } - static void TestMethod(int y) - { - /* - * [|x|] - * xx - */ - } -}"; + static void TestMethod(int y) + { + /* + * [|x|] + * xx + */ + } + } + """; await SetUpEditorAsync(markup, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); await TestServices.InlineRename.ToggleIncludeCommentsAsync(HangMitigatingCancellationToken); @@ -282,56 +285,58 @@ static void TestMethod(int y) await TestServices.Input.SendWithoutActivateAsync([VirtualKeyCode.VK_Y, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -using System; -using System.Collections.Generic; -using System.Linq; + await TestServices.EditorVerifier.TextEqualsAsync(""" + using System; + using System.Collections.Generic; + using System.Linq; -class Program -{ - /// - /// creates a varixable named y xx - /// - /// - static void Main(string[] args) - { - // thix varixable is named y xx - int y$$ = 0; - y = 5; - TestMethod(y); - } + class Program + { + /// + /// creates a varixable named y xx + /// + /// + static void Main(string[] args) + { + // thix varixable is named y xx + int y$$ = 0; + y = 5; + TestMethod(y); + } - static void TestMethod(int y) - { - /* - * y - * xx - */ - } -}", HangMitigatingCancellationToken); + static void TestMethod(int y) + { + /* + * y + * xx + */ + } + } + """, HangMitigatingCancellationToken); } [IdeFact] public async Task VerifyLocalVariableRenameWithStringsUpdated() { - var markup = @" -class Program -{ - static void Main(string[] args) - { - int [|x|]$$ = 0; - [|x|] = 5; - var s = ""[|x|] xx [|x|]""; - var sLiteral = - @"" - [|x|] - xx - [|x|] - ""; - char c = 'x'; - char cUnit = '\u0078'; - } -}"; + var markup = """ + class Program + { + static void Main(string[] args) + { + int [|x|]$$ = 0; + [|x|] = 5; + var s = "[|x|] xx [|x|]"; + var sLiteral = + @" + [|x|] + xx + [|x|] + "; + char c = 'x'; + char cUnit = '\u0078'; + } + } + """; await SetUpEditorAsync(markup, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); @@ -344,44 +349,46 @@ static void Main(string[] args) await TestServices.Input.SendWithoutActivateAsync([VirtualKeyCode.VK_Y, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class Program -{ - static void Main(string[] args) - { - int y$$ = 0; - y = 5; - var s = ""y xx y""; - var sLiteral = - @"" - y - xx - y - ""; - char c = 'x'; - char cUnit = '\u0078'; - } -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class Program + { + static void Main(string[] args) + { + int y$$ = 0; + y = 5; + var s = "y xx y"; + var sLiteral = + @" + y + xx + y + "; + char c = 'x'; + char cUnit = '\u0078'; + } + } + """, HangMitigatingCancellationToken); } [IdeFact] public async Task VerifyOverloadsUpdated() { - var markup = @" -interface I -{ - void [|TestMethod|]$$(int y); - void [|TestMethod|](string y); -} + var markup = """ + interface I + { + void [|TestMethod|]$$(int y); + void [|TestMethod|](string y); + } -class B : I -{ - public virtual void [|TestMethod|](int y) - { } + class B : I + { + public virtual void [|TestMethod|](int y) + { } - public virtual void [|TestMethod|](string y) - { } -}"; + public virtual void [|TestMethod|](string y) + { } + } + """; await SetUpEditorAsync(markup, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); @@ -394,41 +401,44 @@ class B : I await TestServices.Input.SendWithoutActivateAsync([VirtualKeyCode.VK_Y, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -interface I -{ - void y$$(int y); - void y(string y); -} + await TestServices.EditorVerifier.TextEqualsAsync(""" + interface I + { + void y$$(int y); + void y(string y); + } -class B : I -{ - public virtual void y(int y) - { } + class B : I + { + public virtual void y(int y) + { } - public virtual void y(string y) - { } -}", HangMitigatingCancellationToken); + public virtual void y(string y) + { } + } + """, HangMitigatingCancellationToken); } [IdeFact] public async Task VerifyMultiFileRename() { - await SetUpEditorAsync(@" -class $$Program -{ -}", HangMitigatingCancellationToken); + await SetUpEditorAsync(""" + class $$Program + { + } + """, HangMitigatingCancellationToken); await TestServices.SolutionExplorer.AddFileAsync(ProjectName, "Class2.cs", @"", cancellationToken: HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Class2.cs", HangMitigatingCancellationToken); - const string class2Markup = @" -class SomeOtherClass -{ - void M() - { - [|Program|] p = new [|Program|](); - } -}"; + const string class2Markup = """ + class SomeOtherClass + { + void M() + { + [|Program|] p = new [|Program|](); + } + } + """; MarkupTestFile.GetSpans(class2Markup, out var code, out var renameSpans); await TestServices.Editor.SetTextAsync(code, HangMitigatingCancellationToken); @@ -442,91 +452,100 @@ void M() await TestServices.Input.SendWithoutActivateAsync([VirtualKeyCode.VK_Y, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class SomeOtherClass -{ - void M() - { - y$$ p = new y(); - } -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class SomeOtherClass + { + void M() + { + y$$ p = new y(); + } + } + """, HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Class1.cs", HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class y$$ -{ -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class y$$ + { + } + """, HangMitigatingCancellationToken); } [IdeFact] public async Task VerifyRenameCancellation() { - await SetUpEditorAsync(@" -class $$Program -{ -}", HangMitigatingCancellationToken); + await SetUpEditorAsync(""" + class $$Program + { + } + """, HangMitigatingCancellationToken); await TestServices.SolutionExplorer.AddFileAsync(ProjectName, "Class2.cs", @"", cancellationToken: HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Class2.cs", HangMitigatingCancellationToken); - await TestServices.Editor.SetTextAsync(@" -class SomeOtherClass -{ - void M() - { - Program p = new Program(); - } -}", HangMitigatingCancellationToken); + await TestServices.Editor.SetTextAsync(""" + class SomeOtherClass + { + void M() + { + Program p = new Program(); + } + } + """, HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("Program", charsOffset: 0, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); await TestServices.Input.SendWithoutActivateAsync(VirtualKeyCode.VK_Y, HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class SomeOtherClass -{ - void M() - { - y$$ p = new y(); - } -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class SomeOtherClass + { + void M() + { + y$$ p = new y(); + } + } + """, HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Class1.cs", HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class y$$ -{ -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class y$$ + { + } + """, HangMitigatingCancellationToken); await TestServices.Input.SendWithoutActivateAsync(VirtualKeyCode.ESCAPE, HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class Program$$ -{ -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class Program$$ + { + } + """, HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Class2.cs", HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class SomeOtherClass -{ - void M() - { - Program$$ p = new Program(); - } -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class SomeOtherClass + { + void M() + { + Program$$ p = new Program(); + } + } + """, HangMitigatingCancellationToken); } [IdeFact] public async Task VerifyCrossProjectRename() { - await SetUpEditorAsync(@" -$$class RenameRocks -{ - static void Main(string[] args) - { - Class2 c = null; - c.ToString(); - } -}", HangMitigatingCancellationToken); + await SetUpEditorAsync(""" + $$class RenameRocks + { + static void Main(string[] args) + { + Class2 c = null; + c.ToString(); + } + } + """, HangMitigatingCancellationToken); var project1 = ProjectName; var project2 = "Project2"; @@ -545,15 +564,16 @@ public class Class2 { static void Main(string [] args) { } }", HangMitigatingCan await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); await TestServices.Input.SendWithoutActivateAsync([VirtualKeyCode.VK_Y, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class RenameRocks -{ - static void Main(string[] args) - { - y$$ c = null; - c.ToString(); - } -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class RenameRocks + { + static void Main(string[] args) + { + y$$ c = null; + c.ToString(); + } + } + """, HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(project2, "y.cs", HangMitigatingCancellationToken); await TestServices.EditorVerifier.TextEqualsAsync(@" @@ -571,15 +591,16 @@ await TestServices.EditorVerifier.TextEqualsAsync(@" public class Class2 { static void Main(string [] args) { } }$$", HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Class1.cs", HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class RenameRocks -{ - static void Main(string[] args) - { - Class2$$ c = null; - c.ToString(); - } -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class RenameRocks + { + static void Main(string[] args) + { + Class2$$ c = null; + c.ToString(); + } + } + """, HangMitigatingCancellationToken); } [IdeFact] @@ -587,43 +608,46 @@ public async Task VerifyRenameInStandaloneFiles() { await TestServices.SolutionExplorer.CloseSolutionAsync(HangMitigatingCancellationToken); await TestServices.SolutionExplorer.AddStandaloneFileAsync("StandaloneFile1.cs", HangMitigatingCancellationToken); - await TestServices.Editor.SetTextAsync(@" -class Program -{ - void Goo() - { - var ids = 1; - ids = 2; - } -}", HangMitigatingCancellationToken); + await TestServices.Editor.SetTextAsync(""" + class Program + { + void Goo() + { + var ids = 1; + ids = 2; + } + } + """, HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("ids", charsOffset: 0, HangMitigatingCancellationToken); await TestServices.InlineRename.InvokeAsync(HangMitigatingCancellationToken); await TestServices.Input.SendWithoutActivateAsync([VirtualKeyCode.VK_Y, VirtualKeyCode.RETURN], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); - await TestServices.EditorVerifier.TextEqualsAsync(@" -class Program -{ - void Goo() - { - var y$$ = 1; - y = 2; - } -}", HangMitigatingCancellationToken); + await TestServices.EditorVerifier.TextEqualsAsync(""" + class Program + { + void Goo() + { + var y$$ = 1; + y = 2; + } + } + """, HangMitigatingCancellationToken); } [IdeFact, WorkItem("https://github.com/dotnet/roslyn/issues/39617")] public async Task VerifyRenameCaseChange() { await TestServices.SolutionExplorer.AddFileAsync(ProjectName, "Program.cs", -@" -class Program -{ - static void Main(string[] args) - { - } -}", cancellationToken: HangMitigatingCancellationToken); + """ + class Program + { + static void Main(string[] args) + { + } + } + """, cancellationToken: HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Program.cs", HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("Program", charsOffset: 0, HangMitigatingCancellationToken); @@ -634,13 +658,14 @@ static void Main(string[] args) await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); await TestServices.EditorVerifier.TextEqualsAsync( - @" -class p$$rogram -{ - static void Main(string[] args) - { - } -}", HangMitigatingCancellationToken); + """ + class p$$rogram + { + static void Main(string[] args) + { + } + } + """, HangMitigatingCancellationToken); } [IdeFact] @@ -649,11 +674,12 @@ public async Task VerifyTextSync() var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); globalOptions.SetGlobalOption(InlineRenameUIOptionsStorage.UseInlineAdornment, true); await TestServices.SolutionExplorer.AddFileAsync(ProjectName, "Program.cs", -@" -public class Class2 -{ - public int Field123; -}", cancellationToken: HangMitigatingCancellationToken); + """ + public class Class2 + { + public int Field123; + } + """, cancellationToken: HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Program.cs", HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("Field123", charsOffset: 0, HangMitigatingCancellationToken); @@ -661,22 +687,24 @@ public class Class2 await TestServices.Input.SendWithoutActivateAsync(["F", "i"], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); await TestServices.EditorVerifier.TextEqualsAsync( - @" -public class Class2 -{ - public int Fi$$; -}", HangMitigatingCancellationToken); + """ + public class Class2 + { + public int Fi$$; + } + """, HangMitigatingCancellationToken); await TestServices.InlineRename.VerifyStringInFlyout("Fi", HangMitigatingCancellationToken); await TestServices.Input.SendWithoutActivateAsync(["e", "l", "d", "3", "2", "1"], HangMitigatingCancellationToken); await TestServices.Workspace.WaitForRenameAsync(HangMitigatingCancellationToken); await TestServices.EditorVerifier.TextEqualsAsync( - @" -public class Class2 -{ - public int Field321$$; -}", HangMitigatingCancellationToken); + """ + public class Class2 + { + public int Field321$$; + } + """, HangMitigatingCancellationToken); await TestServices.InlineRename.VerifyStringInFlyout("Field321", HangMitigatingCancellationToken); } @@ -685,13 +713,14 @@ public async Task VerifySelectionAsync() { var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync(HangMitigatingCancellationToken); globalOptions.SetGlobalOption(InlineRenameUIOptionsStorage.UseInlineAdornment, true); - var startCode = @" -public class Class2 -{ - public int LongLongField; -}"; + var startCode = """ + public class Class2 + { + public int LongLongField; + } + """; await TestServices.SolutionExplorer.AddFileAsync(ProjectName, "Program.cs", -startCode, cancellationToken: HangMitigatingCancellationToken); + startCode, cancellationToken: HangMitigatingCancellationToken); await TestServices.SolutionExplorer.OpenFileAsync(ProjectName, "Program.cs", HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("LongLongField", charsOffset: 0, HangMitigatingCancellationToken); @@ -700,11 +729,12 @@ await TestServices.SolutionExplorer.AddFileAsync(ProjectName, "Program.cs", await TestServices.Editor.SendExplicitFocusAsync(HangMitigatingCancellationToken); await TestServices.Editor.PlaceCaretAsync("LongLongField", charsOffset: "Long".Length, HangMitigatingCancellationToken); - var markedCode = @" -public class Class2 -{ - public int Long{|selection:Long|}Field; -}"; + var markedCode = """ + public class Class2 + { + public int Long{|selection:Long|}Field; + } + """; MarkupTestFile.GetPositionAndSpans(markedCode, out var _, out int? _, out var spans); var selectedSpan = spans["selection"].Single(); await TestServices.Editor.SetSelectionAsync(selectedSpan, HangMitigatingCancellationToken); @@ -712,11 +742,12 @@ await TestServices.Input.SendWithoutActivateAsync( new InputKey(VirtualKeyCode.BACK, ImmutableArray.Empty), HangMitigatingCancellationToken); await TestServices.Input.SendWithoutActivateAsync(["Other", "Stuff"], HangMitigatingCancellationToken); await TestServices.EditorVerifier.TextEqualsAsync( - @" -public class Class2 -{ - public int LongOtherStuff$$Field; -}", HangMitigatingCancellationToken); + """ + public class Class2 + { + public int LongOtherStuff$$Field; + } + """, HangMitigatingCancellationToken); } [IdeFact(Skip = "https://github.com/dotnet/roslyn/issues/73630"), WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1903953/")]