From 25c21be54168ca54007979bf5391c10f693ecdd5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:05:03 -0700 Subject: [PATCH 01/42] NRT enable code --- .../SymbolFinder.FindReferencesServerCallback.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 181b7893b4cff..1feb74ace7c1d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -2,14 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; @@ -52,13 +51,13 @@ public ValueTask OnCompletedAsync(CancellationToken cancellationToken) public ValueTask OnFindInDocumentStartedAsync(DocumentId documentId, CancellationToken cancellationToken) { - var document = _solution.GetDocument(documentId); + var document = _solution.GetRequiredDocument(documentId); return _progress.OnFindInDocumentStartedAsync(document, cancellationToken); } public ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId, CancellationToken cancellationToken) { - var document = _solution.GetDocument(documentId); + var document = _solution.GetRequiredDocument(documentId); return _progress.OnFindInDocumentCompletedAsync(document, cancellationToken); } @@ -94,8 +93,8 @@ public async ValueTask OnReferenceFoundAsync( SerializableReferenceLocation reference, CancellationToken cancellationToken) { - SymbolGroup symbolGroup; - ISymbol symbol; + SymbolGroup? symbolGroup; + ISymbol? symbol; lock (_gate) { // The definition may not be in the map if we failed to map it over using TryRehydrateAsync in OnDefinitionFoundAsync. From 78aeb05d315bb2a04d51d082f21bc85565754b99 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:06:55 -0700 Subject: [PATCH 02/42] NRT enable code --- .../Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs index 23e1b777576a2..5c13a6846c07f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_FindLiteralReferences.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Threading; using System.Threading.Tasks; From 5cd69649e347b17cfb64a8f429f1b017ba8ea327 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:08:42 -0700 Subject: [PATCH 03/42] NRT enable code --- .../FindSymbols/SymbolFinder_Declarations_SourceDeclarations.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_SourceDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_SourceDeclarations.cs index 4df175ae40acd..9b923bb6f38ef 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_SourceDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_SourceDeclarations.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; From 75e94f8b6ecd748b95f83dae1dffb489a7b75381 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:13:41 -0700 Subject: [PATCH 04/42] NRT enable code --- .../SymbolFinder_Declarations_CustomQueries.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs index 03b165da6bf35..3b1e4f55933be 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_CustomQueries.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -11,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.FindSymbols { @@ -56,15 +55,15 @@ internal static async Task> FindSourceDeclarationsWithCu using (Logger.LogBlock(FunctionId.SymbolFinder_Solution_Predicate_FindSourceDeclarationsAsync, cancellationToken)) { - var result = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var result); foreach (var projectId in solution.ProjectIds) { - var project = solution.GetProject(projectId); + var project = solution.GetRequiredProject(projectId); var symbols = await FindSourceDeclarationsWithCustomQueryAsync(project, query, filter, cancellationToken).ConfigureAwait(false); result.AddRange(symbols); } - return result.ToImmutableAndFree(); + return result.ToImmutable(); } } @@ -103,7 +102,7 @@ internal static async Task> FindSourceDeclarationsWithCu { if (await project.ContainsSymbolsWithNameAsync(query.GetPredicate(), filter, cancellationToken).ConfigureAwait(false)) { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var unfiltered = compilation.GetSymbolsWithName(query.GetPredicate(), filter, cancellationToken) .ToImmutableArray(); From 8d8b172e06569523dc9795d1301acf60fbae76ea Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:15:31 -0700 Subject: [PATCH 05/42] NRT enable code --- .../FindSymbols/SymbolFinder_Declarations_AllDeclarations.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_AllDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_AllDeclarations.cs index 2270320cbf1a3..abafabd2fae30 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_AllDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Declarations_AllDeclarations.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; From 9462d2bffce2b4798c641e74e802bc680715bdc7 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:23:02 -0700 Subject: [PATCH 06/42] NRT enable code --- .../Portable/FindSymbols/ReferenceLocation.cs | 27 ++++++----- .../Core/Portable/Remote/RemoteArguments.cs | 47 +++++++++++-------- 2 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocation.cs b/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocation.cs index 9c495e9b9b007..1c74c357da6c9 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocation.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocation.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -26,7 +24,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols /// /// If the symbol was bound through an alias, then this is the alias that was used. /// - public IAliasSymbol Alias { get; } + public IAliasSymbol? Alias { get; } /// /// The actual source location for a given symbol. @@ -65,8 +63,15 @@ namespace Microsoft.CodeAnalysis.FindSymbols public CandidateReason CandidateReason { get; } - private ReferenceLocation(Document document, IAliasSymbol alias, Location location, bool isImplicit, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary additionalProperties, CandidateReason candidateReason, Location containingStringLocation) - : this() + private ReferenceLocation( + Document document, + IAliasSymbol? alias, + Location location, + bool isImplicit, + SymbolUsageInfo symbolUsageInfo, + ImmutableDictionary additionalProperties, + CandidateReason candidateReason, + Location containingStringLocation) { this.Document = document; this.Alias = alias; @@ -81,7 +86,7 @@ private ReferenceLocation(Document document, IAliasSymbol alias, Location locati /// /// Creates a reference location with the given properties. /// - internal ReferenceLocation(Document document, IAliasSymbol alias, Location location, bool isImplicit, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary additionalProperties, CandidateReason candidateReason) + internal ReferenceLocation(Document document, IAliasSymbol? alias, Location location, bool isImplicit, SymbolUsageInfo symbolUsageInfo, ImmutableDictionary additionalProperties, CandidateReason candidateReason) : this(document, alias, location, isImplicit, symbolUsageInfo, additionalProperties, candidateReason, containingStringLocation: Location.None) { } @@ -111,10 +116,10 @@ internal ReferenceLocation(Document document, Location location, Location contai public static bool operator !=(ReferenceLocation left, ReferenceLocation right) => !(left == right); - public override bool Equals(object obj) + public override bool Equals(object? obj) { - return obj is ReferenceLocation && - Equals((ReferenceLocation)obj); + return obj is ReferenceLocation location && + Equals(location); } public bool Equals(ReferenceLocation other) @@ -140,8 +145,8 @@ public int CompareTo(ReferenceLocation other) { int compare; - var thisPath = this.Location.SourceTree.FilePath; - var otherPath = other.Location.SourceTree.FilePath; + var thisPath = this.Location.SourceTree?.FilePath; + var otherPath = other.Location.SourceTree?.FilePath; if ((compare = StringComparer.OrdinalIgnoreCase.Compare(thisPath, otherPath)) != 0 || (compare = this.Location.SourceSpan.CompareTo(other.Location.SourceSpan)) != 0) diff --git a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs index 2e12f9f7fac59..1c84ad67c5d5f 100644 --- a/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs +++ b/src/Workspaces/Core/Portable/Remote/RemoteArguments.cs @@ -2,17 +2,17 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.FindSymbols; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -35,11 +35,14 @@ public SerializableSymbolAndProjectId(string symbolKeyData, ProjectId projectId) ProjectId = projectId; } - public override bool Equals(object obj) + public override bool Equals(object? obj) => Equals(obj as SerializableSymbolAndProjectId); - public bool Equals(SerializableSymbolAndProjectId other) + public bool Equals(SerializableSymbolAndProjectId? other) { + if (other == null) + return false; + if (this == other) return true; @@ -50,8 +53,9 @@ public bool Equals(SerializableSymbolAndProjectId other) public override int GetHashCode() => Hash.Combine(this.SymbolKeyData, this.ProjectId.GetHashCode()); - public static SerializableSymbolAndProjectId Dehydrate( - IAliasSymbol alias, Document document, CancellationToken cancellationToken) + [return: NotNullIfNotNull("alias")] + public static SerializableSymbolAndProjectId? Dehydrate( + IAliasSymbol? alias, Document document, CancellationToken cancellationToken) { return alias == null ? null @@ -72,7 +76,7 @@ public static SerializableSymbolAndProjectId Create(ISymbol symbol, Project proj public static bool TryCreate( ISymbol symbol, Solution solution, CancellationToken cancellationToken, - out SerializableSymbolAndProjectId result) + [NotNullWhen(true)] out SerializableSymbolAndProjectId? result) { var project = solution.GetOriginatingProject(symbol); if (project == null) @@ -86,7 +90,7 @@ public static bool TryCreate( public static bool TryCreate( ISymbol symbol, Project project, CancellationToken cancellationToken, - out SerializableSymbolAndProjectId result) + [NotNullWhen(true)] out SerializableSymbolAndProjectId? result) { if (!SymbolKey.CanCreate(symbol, cancellationToken)) { @@ -98,12 +102,12 @@ public static bool TryCreate( return true; } - public async Task TryRehydrateAsync( + public async Task TryRehydrateAsync( Solution solution, CancellationToken cancellationToken) { var projectId = ProjectId; - var project = solution.GetProject(projectId); - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var project = solution.GetRequiredProject(projectId); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); // The server and client should both be talking about the same compilation. As such // locations in symbols are save to resolve as we rehydrate the SymbolKey. @@ -134,7 +138,7 @@ internal readonly struct SerializableReferenceLocation public readonly DocumentId Document; [DataMember(Order = 1)] - public readonly SerializableSymbolAndProjectId Alias; + public readonly SerializableSymbolAndProjectId? Alias; [DataMember(Order = 2)] public readonly TextSpan Location; @@ -153,7 +157,7 @@ internal readonly struct SerializableReferenceLocation public SerializableReferenceLocation( DocumentId document, - SerializableSymbolAndProjectId alias, + SerializableSymbolAndProjectId? alias, TextSpan location, bool isImplicit, SymbolUsageInfo symbolUsageInfo, @@ -185,8 +189,8 @@ public static SerializableReferenceLocation Dehydrate( public async Task RehydrateAsync( Solution solution, CancellationToken cancellationToken) { - var document = await solution.GetDocumentAsync(this.Document, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); - var syntaxTree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var document = await solution.GetRequiredDocumentAsync(this.Document, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var aliasSymbol = await RehydrateAliasAsync(solution, cancellationToken).ConfigureAwait(false); var additionalProperties = this.AdditionalProperties; return new ReferenceLocation( @@ -199,7 +203,7 @@ public async Task RehydrateAsync( candidateReason: CandidateReason); } - private async Task RehydrateAliasAsync( + private async Task RehydrateAliasAsync( Solution solution, CancellationToken cancellationToken) { if (Alias == null) @@ -223,15 +227,18 @@ public SerializableSymbolGroup(HashSet symbols) Symbols = new HashSet(symbols); } - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is SerializableSymbolGroup group && Equals(group); - public bool Equals(SerializableSymbolGroup other) + public bool Equals(SerializableSymbolGroup? other) { + if (other == null) + return false; + if (this == other) return true; - return other != null && this.Symbols.SetEquals(other.Symbols); + return this.Symbols.SetEquals(other.Symbols); } public override int GetHashCode() @@ -241,7 +248,7 @@ public override int GetHashCode() var hashCode = 0; foreach (var symbol in Symbols) hashCode += symbol.SymbolKeyData.GetHashCode(); - _hashCode = hashCode; + _hashCode = hashCode == 0 ? 1 : hashCode; } return _hashCode; From 98d01d7b6ef9517f9ede056cc7dd94e9892171ed Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:36:50 -0700 Subject: [PATCH 07/42] NRT enable code --- .../FindSymbols/SymbolFinder_Helpers.cs | 2 +- .../FindSymbols/SymbolFinder_Hierarchy.cs | 36 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs index e8d91427d3569..f6601c1280ca5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Helpers.cs @@ -32,7 +32,7 @@ Accessibility.Protected or internal static async Task OriginalSymbolsMatchAsync( Solution solution, - ISymbol searchSymbol, + ISymbol? searchSymbol, ISymbol? symbolToMatch, CancellationToken cancellationToken) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs index 8db349f2014aa..f1bc8827bf03d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder_Hierarchy.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -25,7 +23,7 @@ public static partial class SymbolFinder /// Find symbols for members that override the specified member symbol. /// public static async Task> FindOverridesAsync( - ISymbol symbol, Solution solution, IImmutableSet projects = null, CancellationToken cancellationToken = default) + ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) { return await FindOverridesArrayAsync(symbol, solution, projects, cancellationToken).ConfigureAwait(false); } @@ -35,11 +33,11 @@ public static async Task> FindOverridesAsync( /// Use this overload to avoid boxing the result into an . /// internal static async Task> FindOverridesArrayAsync( - ISymbol symbol, Solution solution, IImmutableSet projects = null, CancellationToken cancellationToken = default) + ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) { var results = ArrayBuilder.GetInstance(); - symbol = symbol?.OriginalDefinition; + symbol = symbol.OriginalDefinition; if (symbol.IsOverridable()) { // To find the overrides, we need to walk down the type hierarchy and check all @@ -81,7 +79,7 @@ internal static async Task IsOverrideAsync(Solution solution, ISymbol memb /// Find symbols for declarations that implement members of the specified interface symbol /// public static async Task> FindImplementedInterfaceMembersAsync( - ISymbol symbol, Solution solution, IImmutableSet projects = null, CancellationToken cancellationToken = default) + ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) { return await FindImplementedInterfaceMembersArrayAsync(symbol, solution, projects, cancellationToken).ConfigureAwait(false); } @@ -97,7 +95,7 @@ internal static Task> FindImplementedInterfaceMembersArr /// Use this overload to avoid boxing the result into an . /// internal static async Task> FindImplementedInterfaceMembersArrayAsync( - ISymbol symbol, Solution solution, IImmutableSet projects, CancellationToken cancellationToken) + ISymbol symbol, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken) { // Member can only implement interface members if it is an explicit member, or if it is // public @@ -139,8 +137,12 @@ internal static async Task> FindImplementedInterfaceMemb // make sure that the interface even contains a symbol with the same // name as the symbol we're looking for. var nameToLookFor = symbol.IsPropertyAccessor() - ? ((IMethodSymbol)symbol).AssociatedSymbol.Name + ? ((IMethodSymbol)symbol).AssociatedSymbol?.Name : symbol.Name; + + if (nameToLookFor == null) + continue; + if (interfaceType.MemberNames.Contains(nameToLookFor)) { foreach (var m in interfaceType.GetMembers(symbol.Name)) @@ -183,7 +185,7 @@ internal static async Task> FindImplementedInterfaceMemb /// The derived types of the symbol. The symbol passed in is not included in this list. [EditorBrowsable(EditorBrowsableState.Never)] public static Task> FindDerivedClassesAsync( - INamedTypeSymbol type, Solution solution, IImmutableSet projects, CancellationToken cancellationToken) + INamedTypeSymbol type, Solution solution, IImmutableSet? projects, CancellationToken cancellationToken) { return FindDerivedClassesAsync(type, solution, transitive: true, projects, cancellationToken); } @@ -201,7 +203,7 @@ public static Task> FindDerivedClassesAsync( /// The derived types of the symbol. The symbol passed in is not included in this list. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static async Task> FindDerivedClassesAsync( - INamedTypeSymbol type, Solution solution, bool transitive = true, IImmutableSet projects = null, CancellationToken cancellationToken = default) + INamedTypeSymbol type, Solution solution, bool transitive = true, IImmutableSet? projects = null, CancellationToken cancellationToken = default) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters { if (type == null) @@ -216,7 +218,7 @@ public static async Task> FindDerivedClassesAsync( /// /// Use this overload to avoid boxing the result into an . internal static async Task> FindDerivedClassesArrayAsync( - INamedTypeSymbol type, Solution solution, bool transitive, IImmutableSet projects = null, CancellationToken cancellationToken = default) + INamedTypeSymbol type, Solution solution, bool transitive, IImmutableSet? projects = null, CancellationToken cancellationToken = default) { var types = await DependentTypeFinder.FindTypesAsync( type, solution, projects, transitive, DependentTypesKind.DerivedClasses, cancellationToken).ConfigureAwait(false); @@ -237,7 +239,7 @@ internal static async Task> FindDerivedClassesA /// The derived interfaces of the symbol. The symbol passed in is not included in this list. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static async Task> FindDerivedInterfacesAsync( - INamedTypeSymbol type, Solution solution, bool transitive = true, IImmutableSet projects = null, CancellationToken cancellationToken = default) + INamedTypeSymbol type, Solution solution, bool transitive = true, IImmutableSet? projects = null, CancellationToken cancellationToken = default) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters { if (type == null) @@ -252,7 +254,7 @@ public static async Task> FindDerivedInterfacesAsy /// /// Use this overload to avoid boxing the result into an . internal static async Task> FindDerivedInterfacesArrayAsync( - INamedTypeSymbol type, Solution solution, bool transitive, IImmutableSet projects = null, CancellationToken cancellationToken = default) + INamedTypeSymbol type, Solution solution, bool transitive, IImmutableSet? projects = null, CancellationToken cancellationToken = default) { var types = await DependentTypeFinder.FindTypesAsync( type, solution, projects, transitive, DependentTypesKind.DerivedInterfaces, cancellationToken).ConfigureAwait(false); @@ -273,7 +275,7 @@ internal static async Task> FindDerivedInterfac /// The projects to search. Can be null to search the entire solution. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters public static async Task> FindImplementationsAsync( - INamedTypeSymbol type, Solution solution, bool transitive = true, IImmutableSet projects = null, CancellationToken cancellationToken = default) + INamedTypeSymbol type, Solution solution, bool transitive = true, IImmutableSet? projects = null, CancellationToken cancellationToken = default) #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters { if (type == null) @@ -288,7 +290,7 @@ public static async Task> FindImplementationsAsync /// /// Use this overload to avoid boxing the result into an . internal static async Task> FindImplementationsArrayAsync( - INamedTypeSymbol type, Solution solution, bool transitive, IImmutableSet projects = null, CancellationToken cancellationToken = default) + INamedTypeSymbol type, Solution solution, bool transitive, IImmutableSet? projects = null, CancellationToken cancellationToken = default) { var types = await DependentTypeFinder.FindTypesAsync( type, solution, projects, transitive, DependentTypesKind.ImplementingTypes, cancellationToken).ConfigureAwait(false); @@ -302,7 +304,7 @@ internal static async Task> FindImplementations /// cref="INamedTypeSymbol"/> this will be both immediate and transitive implementations. /// public static async Task> FindImplementationsAsync( - ISymbol symbol, Solution solution, IImmutableSet projects = null, CancellationToken cancellationToken = default) + ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) { if (symbol == null) throw new ArgumentNullException(nameof(symbol)); @@ -326,7 +328,7 @@ public static async Task> FindImplementationsAsync( /// Use this overload to avoid boxing the result into an . /// internal static async Task> FindMemberImplementationsArrayAsync( - ISymbol symbol, Solution solution, IImmutableSet projects = null, CancellationToken cancellationToken = default) + ISymbol symbol, Solution solution, IImmutableSet? projects = null, CancellationToken cancellationToken = default) { if (!symbol.IsImplementableMember()) return ImmutableArray.Empty; From ffd34209a1e616cae2670c873058ae664f713007 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:40:24 -0700 Subject: [PATCH 08/42] NRT enable code --- .../Portable/EmbeddedLanguages/IEmbeddedLanguagesProvider.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/EmbeddedLanguages/IEmbeddedLanguagesProvider.cs b/src/Workspaces/Core/Portable/EmbeddedLanguages/IEmbeddedLanguagesProvider.cs index 8dc483d351ccd..ddb7185cf11dd 100644 --- a/src/Workspaces/Core/Portable/EmbeddedLanguages/IEmbeddedLanguagesProvider.cs +++ b/src/Workspaces/Core/Portable/EmbeddedLanguages/IEmbeddedLanguagesProvider.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using Microsoft.CodeAnalysis.Host; From 4c13017c7e62340725b63cef7f56817e0d0948c9 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:42:59 -0700 Subject: [PATCH 09/42] NRT enable code --- .../FindSymbols/SyntaxTree/SyntaxTreeIndex.LiteralInfo.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.LiteralInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.LiteralInfo.cs index e918bba1aaefb..ce2f3f57e3f9c 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.LiteralInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.LiteralInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; From 19e576917f3c9ad8c568014f1e0e374a5028bc1a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:45:55 -0700 Subject: [PATCH 10/42] NRT enable code --- .../FindSymbols/SyntaxTree/SyntaxTreeIndex.IdentifierInfo.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.IdentifierInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.IdentifierInfo.cs index 4608b1cb2f7a9..4d69a45bc65f0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.IdentifierInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SyntaxTree/SyntaxTreeIndex.IdentifierInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using Microsoft.CodeAnalysis.Shared.Utilities; using Roslyn.Utilities; From 9fe660cdfd1646d2fd3f4ae8244a505768da44bf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:49:08 -0700 Subject: [PATCH 11/42] NRT enable code --- .../Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs | 6 +++--- .../FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs | 6 +++--- .../FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs | 8 +++----- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs index e14c4313f6212..a737a7de85f65 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs @@ -98,7 +98,7 @@ private SymbolTreeInfo( ImmutableArray sortedNodes, Task spellCheckerTask, OrderPreservingMultiDictionary inheritanceMap, - MultiDictionary receiverTypeNameToExtensionMethodMap) + MultiDictionary? receiverTypeNameToExtensionMethodMap) : this(checksum, sortedNodes, spellCheckerTask, CreateIndexBasedInheritanceMap(sortedNodes, inheritanceMap), receiverTypeNameToExtensionMethodMap) @@ -496,14 +496,14 @@ private static SymbolTreeInfo CreateSymbolTreeInfo( HostWorkspaceServices services, SolutionKey solutionKey, Checksum checksum, string filePath, ImmutableArray unsortedNodes, OrderPreservingMultiDictionary inheritanceMap, - MultiDictionary simpleMethods) + MultiDictionary? receiverTypeNameToExtensionMethodMap) { SortNodes(unsortedNodes, out var sortedNodes); var createSpellCheckerTask = GetSpellCheckerAsync( services, solutionKey, checksum, filePath, sortedNodes); return new SymbolTreeInfo( - checksum, sortedNodes, createSpellCheckerTask, inheritanceMap, simpleMethods); + checksum, sortedNodes, createSpellCheckerTask, inheritanceMap, receiverTypeNameToExtensionMethodMap); } private static OrderPreservingMultiDictionary CreateIndexBasedInheritanceMap( diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index 1c1e26f4d48f6..693d892bc16fe 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -300,11 +300,11 @@ internal SymbolTreeInfo Create() } } - var extensionMethodsMap = new MultiDictionary(); - var unsortedNodes = GenerateUnsortedNodes(extensionMethodsMap); + var receiverTypeNameToExtensionMethodMap = new MultiDictionary(); + var unsortedNodes = GenerateUnsortedNodes(receiverTypeNameToExtensionMethodMap); return CreateSymbolTreeInfo( - _services, _solutionKey, _checksum, _reference.FilePath, unsortedNodes, _inheritanceMap, extensionMethodsMap); + _services, _solutionKey, _checksum, _reference.FilePath, unsortedNodes, _inheritanceMap, receiverTypeNameToExtensionMethodMap); } public void Dispose() diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs index acf388e910315..de1d63f437d4b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Source.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Linq; using System.Runtime.CompilerServices; @@ -37,7 +35,7 @@ public static Task GetInfoForSourceAssemblyAsync( var solution = project.Solution; var services = solution.Workspace.Services; var solutionKey = SolutionKey.ToSolutionKey(solution); - var projectFilePath = project.FilePath; + var projectFilePath = project.FilePath ?? ""; var result = TryLoadOrCreateAsync( services, @@ -124,9 +122,9 @@ internal static async Task CreateSourceSymbolTreeInfoAsync( var solutionKey = SolutionKey.ToSolutionKey(solution); return CreateSymbolTreeInfo( - services, solutionKey, checksum, project.FilePath, unsortedNodes.ToImmutableAndFree(), + services, solutionKey, checksum, project.FilePath ?? "", unsortedNodes.ToImmutableAndFree(), inheritanceMap: new OrderPreservingMultiDictionary(), - simpleMethods: null); + receiverTypeNameToExtensionMethodMap: null); } // generate nodes for the global namespace an all descendants From c89c3db503508bf1ae14b10ba59954ef342bd053 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 10:53:17 -0700 Subject: [PATCH 12/42] In progress --- .../SymbolTree/SymbolTreeInfo_Metadata.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index 693d892bc16fe..d56c54ae82616 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -43,7 +41,7 @@ private static string GetMetadataNameWithoutBackticks(MetadataReader reader, Str } } - public static MetadataId GetMetadataIdNoThrow(PortableExecutableReference reference) + public static MetadataId? GetMetadataIdNoThrow(PortableExecutableReference reference) { try { @@ -55,7 +53,7 @@ public static MetadataId GetMetadataIdNoThrow(PortableExecutableReference refere } } - private static Metadata GetMetadataNoThrow(PortableExecutableReference reference) + private static Metadata? GetMetadataNoThrow(PortableExecutableReference reference) { try { @@ -67,7 +65,7 @@ private static Metadata GetMetadataNoThrow(PortableExecutableReference reference } } - public static ValueTask GetInfoForMetadataReferenceAsync( + public static ValueTask GetInfoForMetadataReferenceAsync( Solution solution, PortableExecutableReference reference, bool loadOnly, CancellationToken cancellationToken) { @@ -82,7 +80,7 @@ public static ValueTask GetInfoForMetadataReferenceAsync( /// Note: will never return null; /// [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", OftenCompletesSynchronously = true)] - public static async ValueTask GetInfoForMetadataReferenceAsync( + public static async ValueTask GetInfoForMetadataReferenceAsync( Solution solution, PortableExecutableReference reference, Checksum checksum, @@ -114,13 +112,13 @@ public static async ValueTask GetInfoForMetadataReferenceAsync( solution.Workspace.Services, SolutionKey.ToSolutionKey(solution), reference, checksum, metadata, cancellationToken).ConfigureAwait(false); } - public static Task TryGetCachedInfoForMetadataReferenceIgnoreChecksumAsync(PortableExecutableReference reference, CancellationToken cancellationToken) + public static async Task TryGetCachedInfoForMetadataReferenceIgnoreChecksumAsync(PortableExecutableReference reference, CancellationToken cancellationToken) { var metadataId = GetMetadataIdNoThrow(reference); - if (metadataId != null && s_metadataIdToInfo.TryGetValue(metadataId, out var infoTask)) - return infoTask.GetValueAsync(cancellationToken); + if (metadataId == null || !s_metadataIdToInfo.TryGetValue(metadataId, out var infoTask)) + return null; - return SpecializedTasks.Null(); + return await infoTask.GetValueAsync(cancellationToken).ConfigureAwait(false); } private static async Task GetInfoForMetadataReferenceSlowAsync( @@ -139,7 +137,7 @@ private static async Task GetInfoForMetadataReferenceSlowAsync( var asyncLazy = s_metadataIdToInfo.GetValue( metadata.Id, id => new AsyncLazy( - c => TryCreateMetadataSymbolTreeInfoAsync(services, solutionKey, reference, checksum, c), + c => CreateMetadataSymbolTreeInfoAsync(services, solutionKey, reference, checksum, c), cacheResult: true)); return await asyncLazy.GetValueAsync(cancellationToken).ConfigureAwait(false); @@ -165,7 +163,7 @@ private static Checksum GetMetadataChecksumSlow(Solution solution, PortableExecu { return ChecksumCache.GetOrCreate(reference, _ => { - var serializer = solution.Workspace.Services.GetService(); + var serializer = solution.Workspace.Services.GetRequiredService(); var checksum = serializer.CreateChecksum(reference, cancellationToken); // Include serialization format version in our checksum. That way if the @@ -175,14 +173,14 @@ private static Checksum GetMetadataChecksumSlow(Solution solution, PortableExecu }); } - private static Task TryCreateMetadataSymbolTreeInfoAsync( + private static Task CreateMetadataSymbolTreeInfoAsync( HostWorkspaceServices services, SolutionKey solutionKey, PortableExecutableReference reference, Checksum checksum, CancellationToken cancellationToken) { - var filePath = reference.FilePath; + var filePath = reference.FilePath ?? ""; var result = TryLoadOrCreateAsync( services, @@ -219,7 +217,7 @@ private struct MetadataInfoCreator : IDisposable private readonly MetadataNode _rootNode; // The metadata reader for the current metadata in the PEReference. - private MetadataReader _metadataReader; + private MetadataReader? _metadataReader; // The set of type definitions we've read out of the current metadata reader. private readonly List _allTypeDefinitions = new(); @@ -249,7 +247,7 @@ public MetadataInfoCreator( _rootNode = MetadataNode.Allocate(name: ""); } - private static ImmutableArray GetModuleMetadata(Metadata metadata) + private static ImmutableArray GetModuleMetadata(Metadata? metadata) { try { @@ -304,7 +302,7 @@ internal SymbolTreeInfo Create() var unsortedNodes = GenerateUnsortedNodes(receiverTypeNameToExtensionMethodMap); return CreateSymbolTreeInfo( - _services, _solutionKey, _checksum, _reference.FilePath, unsortedNodes, _inheritanceMap, receiverTypeNameToExtensionMethodMap); + _services, _solutionKey, _checksum, _reference.FilePath ?? "", unsortedNodes, _inheritanceMap, receiverTypeNameToExtensionMethodMap); } public void Dispose() @@ -325,6 +323,7 @@ public void Dispose() private void GenerateMetadataNodes() { + Contract.ThrowIfNull(_metadataReader); var globalNamespace = _metadataReader.GetNamespaceDefinitionRoot(); var definitionMap = OrderPreservingMultiDictionary.GetInstance(); try From 8bb81a29bf5c5a0b37701ef3802d2c697329b3a5 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:00:26 -0700 Subject: [PATCH 13/42] NRT enable code --- .../FindReferences/DependentTypeFinder.cs | 3 + .../SymbolTree/SymbolTreeInfo_Metadata.cs | 134 ++++++++++-------- 2 files changed, 80 insertions(+), 57 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs index 35e77017840f0..adc1dd868cc1a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypeFinder.cs @@ -424,6 +424,9 @@ private static async Task AddMatchingMetadataTypesInMetadataReferenceAsync( var symbolTreeInfo = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( project.Solution, reference, loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false); + // This will always be non-null since we pass loadOnly: false above. + Contract.ThrowIfNull(symbolTreeInfo); + // For each type we care about, see if we can find any derived types // in this index. foreach (var metadataType in metadataTypes) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index d56c54ae82616..64b9d1f33fa88 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Reflection; @@ -216,9 +217,6 @@ private struct MetadataInfoCreator : IDisposable private readonly OrderPreservingMultiDictionary _parentToChildren; private readonly MetadataNode _rootNode; - // The metadata reader for the current metadata in the PEReference. - private MetadataReader? _metadataReader; - // The set of type definitions we've read out of the current metadata reader. private readonly List _allTypeDefinitions = new(); @@ -239,7 +237,6 @@ public MetadataInfoCreator( _solutionKey = solutionKey; _checksum = checksum; _reference = reference; - _metadataReader = null; _containsExtensionsMethod = false; _inheritanceMap = OrderPreservingMultiDictionary.GetInstance(); @@ -276,17 +273,17 @@ internal SymbolTreeInfo Create() { try { - _metadataReader = moduleMetadata.GetMetadataReader(); + var metadataReader = moduleMetadata.GetMetadataReader(); // First, walk all the symbols from metadata, populating the parentToChilren // map accordingly. - GenerateMetadataNodes(); + GenerateMetadataNodes(metadataReader); // Now, once we populated the initial map, go and get all the inheritance // information for all the types in the metadata. This may refer to // types that we haven't seen yet. We'll add those types to the parentToChildren // map accordingly. - PopulateInheritanceMap(); + PopulateInheritanceMap(metadataReader); // Clear the set of type definitions we read out of this piece of metadata. _allTypeDefinitions.Clear(); @@ -321,17 +318,16 @@ public void Dispose() _inheritanceMap.Free(); } - private void GenerateMetadataNodes() + private void GenerateMetadataNodes(MetadataReader metadataReader) { - Contract.ThrowIfNull(_metadataReader); - var globalNamespace = _metadataReader.GetNamespaceDefinitionRoot(); + var globalNamespace = metadataReader.GetNamespaceDefinitionRoot(); var definitionMap = OrderPreservingMultiDictionary.GetInstance(); try { - LookupMetadataDefinitions(globalNamespace, definitionMap); + LookupMetadataDefinitions(metadataReader, globalNamespace, definitionMap); foreach (var (name, definitions) in definitionMap) - GenerateMetadataNodes(_rootNode, name, definitions); + GenerateMetadataNodes(metadataReader, _rootNode, name, definitions); } finally { @@ -340,6 +336,7 @@ private void GenerateMetadataNodes() } private void GenerateMetadataNodes( + MetadataReader metadataReader, MetadataNode parentNode, string nodeName, OrderPreservingMultiDictionary.ValueSet definitionsWithSameName) @@ -364,11 +361,11 @@ private void GenerateMetadataNodes( _extensionMethodToParameterTypeInfo.Add(childNode, definition.ReceiverTypeInfo); } - LookupMetadataDefinitions(definition, definitionMap); + LookupMetadataDefinitions(metadataReader, definition, definitionMap); } foreach (var (name, definitions) in definitionMap) - GenerateMetadataNodes(childNode, name, definitions); + GenerateMetadataNodes(metadataReader, childNode, name, definitions); } finally { @@ -377,21 +374,23 @@ private void GenerateMetadataNodes( } private void LookupMetadataDefinitions( + MetadataReader metadataReader, MetadataDefinition definition, OrderPreservingMultiDictionary definitionMap) { switch (definition.Kind) { case MetadataDefinitionKind.Namespace: - LookupMetadataDefinitions(definition.Namespace, definitionMap); + LookupMetadataDefinitions(metadataReader, definition.Namespace, definitionMap); break; case MetadataDefinitionKind.Type: - LookupMetadataDefinitions(definition.Type, definitionMap); + LookupMetadataDefinitions(metadataReader, definition.Type, definitionMap); break; } } private void LookupMetadataDefinitions( + MetadataReader metadataReader, TypeDefinition typeDefinition, OrderPreservingMultiDictionary definitionMap) { @@ -406,7 +405,7 @@ private void LookupMetadataDefinitions( { foreach (var child in typeDefinition.GetMethods()) { - var method = _metadataReader.GetMethodDefinition(child); + var method = metadataReader.GetMethodDefinition(child); if ((method.Attributes & MethodAttributes.SpecialName) != 0 || (method.Attributes & MethodAttributes.RTSpecialName) != 0) { @@ -422,8 +421,8 @@ private void LookupMetadataDefinitions( method.GetCustomAttributes().Count > 0) { // Decode method signature to get the receiver type name (i.e. type name for the first parameter) - var blob = _metadataReader.GetBlobReader(method.Signature); - var decoder = new SignatureDecoder(ParameterTypeInfoProvider.Instance, _metadataReader, genericContext: null); + var blob = metadataReader.GetBlobReader(method.Signature); + var decoder = new SignatureDecoder(ParameterTypeInfoProvider.Instance, metadataReader, genericContext: null!); var signature = decoder.DecodeMethodSignature(ref blob); // It'd be good if we don't need to go through all parameters and make unnecessary allocations. @@ -432,7 +431,7 @@ private void LookupMetadataDefinitions( { _containsExtensionsMethod = true; var firstParameterTypeInfo = signature.ParameterTypes[0]; - var definition = new MetadataDefinition(MetadataDefinitionKind.Member, _metadataReader.GetString(method.Name), firstParameterTypeInfo); + var definition = new MetadataDefinition(MetadataDefinitionKind.Member, metadataReader.GetString(method.Name), firstParameterTypeInfo); definitionMap.Add(definition.Name, definition); } } @@ -441,7 +440,7 @@ private void LookupMetadataDefinitions( foreach (var child in typeDefinition.GetNestedTypes()) { - var type = _metadataReader.GetTypeDefinition(child); + var type = metadataReader.GetTypeDefinition(child); // We don't include internals from metadata assemblies. It's less likely that // a project would have IVT to it and so it helps us save on memory. It also @@ -449,7 +448,7 @@ private void LookupMetadataDefinitions( // dll was obfuscated. if (IsPublic(type.Attributes)) { - var definition = MetadataDefinition.Create(_metadataReader, type); + var definition = MetadataDefinition.Create(metadataReader, type); definitionMap.Add(definition.Name, definition); _allTypeDefinitions.Add(definition); } @@ -457,21 +456,22 @@ private void LookupMetadataDefinitions( } private void LookupMetadataDefinitions( + MetadataReader metadataReader, NamespaceDefinition namespaceDefinition, OrderPreservingMultiDictionary definitionMap) { foreach (var child in namespaceDefinition.NamespaceDefinitions) { - var definition = MetadataDefinition.Create(_metadataReader, child); + var definition = MetadataDefinition.Create(metadataReader, child); definitionMap.Add(definition.Name, definition); } foreach (var child in namespaceDefinition.TypeDefinitions) { - var typeDefinition = _metadataReader.GetTypeDefinition(child); + var typeDefinition = metadataReader.GetTypeDefinition(child); if (IsPublic(typeDefinition.Attributes)) { - var definition = MetadataDefinition.Create(_metadataReader, typeDefinition); + var definition = MetadataDefinition.Create(metadataReader, typeDefinition); definitionMap.Add(definition.Name, definition); _allTypeDefinitions.Add(definition); } @@ -484,16 +484,18 @@ private static bool IsPublic(TypeAttributes attributes) return masked is TypeAttributes.Public or TypeAttributes.NestedPublic; } - private void PopulateInheritanceMap() + private void PopulateInheritanceMap(MetadataReader metadataReader) { foreach (var typeDefinition in _allTypeDefinitions) { Debug.Assert(typeDefinition.Kind == MetadataDefinitionKind.Type); - PopulateInheritance(typeDefinition); + PopulateInheritance(metadataReader, typeDefinition); } } - private void PopulateInheritance(MetadataDefinition metadataTypeDefinition) + private void PopulateInheritance( + MetadataReader metadataReader, + MetadataDefinition metadataTypeDefinition) { var derivedTypeDefinition = metadataTypeDefinition.Type; var interfaceImplHandles = derivedTypeDefinition.GetInterfaceImplementations(); @@ -506,19 +508,20 @@ private void PopulateInheritance(MetadataDefinition metadataTypeDefinition) var derivedTypeSimpleName = metadataTypeDefinition.Name; - PopulateInheritance(derivedTypeSimpleName, derivedTypeDefinition.BaseType); + PopulateInheritance(metadataReader, derivedTypeSimpleName, derivedTypeDefinition.BaseType); foreach (var interfaceImplHandle in interfaceImplHandles) { if (!interfaceImplHandle.IsNil) { - var interfaceImpl = _metadataReader.GetInterfaceImplementation(interfaceImplHandle); - PopulateInheritance(derivedTypeSimpleName, interfaceImpl.Interface); + var interfaceImpl = metadataReader.GetInterfaceImplementation(interfaceImplHandle); + PopulateInheritance(metadataReader, derivedTypeSimpleName, interfaceImpl.Interface); } } } private void PopulateInheritance( + MetadataReader metadataReader, string derivedTypeSimpleName, EntityHandle baseTypeOrInterfaceHandle) { @@ -530,7 +533,7 @@ private void PopulateInheritance( var baseTypeNameParts = s_stringListPool.Allocate(); try { - AddBaseTypeNameParts(baseTypeOrInterfaceHandle, baseTypeNameParts); + AddBaseTypeNameParts(metadataReader, baseTypeOrInterfaceHandle, baseTypeNameParts); if (baseTypeNameParts.Count > 0 && baseTypeNameParts.TrueForAll(s_isNotNullOrEmpty)) { @@ -554,45 +557,50 @@ private void PopulateInheritance( } private void AddBaseTypeNameParts( + MetadataReader metadataReader, EntityHandle baseTypeOrInterfaceHandle, List simpleNames) { - var typeDefOrRefHandle = GetTypeDefOrRefHandle(baseTypeOrInterfaceHandle); + var typeDefOrRefHandle = GetTypeDefOrRefHandle(metadataReader, baseTypeOrInterfaceHandle); if (typeDefOrRefHandle.Kind == HandleKind.TypeDefinition) { - AddTypeDefinitionNameParts((TypeDefinitionHandle)typeDefOrRefHandle, simpleNames); + AddTypeDefinitionNameParts(metadataReader, (TypeDefinitionHandle)typeDefOrRefHandle, simpleNames); } else if (typeDefOrRefHandle.Kind == HandleKind.TypeReference) { - AddTypeReferenceNameParts((TypeReferenceHandle)typeDefOrRefHandle, simpleNames); + AddTypeReferenceNameParts(metadataReader, (TypeReferenceHandle)typeDefOrRefHandle, simpleNames); } } private void AddTypeDefinitionNameParts( - TypeDefinitionHandle handle, List simpleNames) + MetadataReader metadataReader, + TypeDefinitionHandle handle, + List simpleNames) { - var typeDefinition = _metadataReader.GetTypeDefinition(handle); + var typeDefinition = metadataReader.GetTypeDefinition(handle); var declaringType = typeDefinition.GetDeclaringType(); if (declaringType.IsNil) { // Not a nested type, just add the containing namespace. - AddNamespaceParts(typeDefinition.NamespaceDefinition, simpleNames); + AddNamespaceParts(metadataReader, typeDefinition.NamespaceDefinition, simpleNames); } else { // We're a nested type, recurse and add the type we're declared in. // It will handle adding the namespace properly. - AddTypeDefinitionNameParts(declaringType, simpleNames); + AddTypeDefinitionNameParts(metadataReader, declaringType, simpleNames); } // Now add the simple name of the type itself. - simpleNames.Add(GetMetadataNameWithoutBackticks(_metadataReader, typeDefinition.Name)); + simpleNames.Add(GetMetadataNameWithoutBackticks(metadataReader, typeDefinition.Name)); } - private void AddNamespaceParts( - StringHandle namespaceHandle, List simpleNames) + private static void AddNamespaceParts( + MetadataReader metadataReader, + StringHandle namespaceHandle, + List simpleNames) { - var blobReader = _metadataReader.GetBlobReader(namespaceHandle); + var blobReader = metadataReader.GetBlobReader(namespaceHandle); while (true) { @@ -622,26 +630,33 @@ private void AddNamespaceParts( } private void AddNamespaceParts( - NamespaceDefinitionHandle namespaceHandle, List simpleNames) + MetadataReader metadataReader, + NamespaceDefinitionHandle namespaceHandle, + List simpleNames) { if (namespaceHandle.IsNil) { return; } - var namespaceDefinition = _metadataReader.GetNamespaceDefinition(namespaceHandle); - AddNamespaceParts(namespaceDefinition.Parent, simpleNames); - simpleNames.Add(_metadataReader.GetString(namespaceDefinition.Name)); + var namespaceDefinition = metadataReader.GetNamespaceDefinition(namespaceHandle); + AddNamespaceParts(metadataReader, namespaceDefinition.Parent, simpleNames); + simpleNames.Add(metadataReader.GetString(namespaceDefinition.Name)); } - private void AddTypeReferenceNameParts(TypeReferenceHandle handle, List simpleNames) + private static void AddTypeReferenceNameParts( + MetadataReader metadataReader, + TypeReferenceHandle handle, + List simpleNames) { - var typeReference = _metadataReader.GetTypeReference(handle); - AddNamespaceParts(typeReference.Namespace, simpleNames); - simpleNames.Add(GetMetadataNameWithoutBackticks(_metadataReader, typeReference.Name)); + var typeReference = metadataReader.GetTypeReference(handle); + AddNamespaceParts(metadataReader, typeReference.Namespace, simpleNames); + simpleNames.Add(GetMetadataNameWithoutBackticks(metadataReader, typeReference.Name)); } - private EntityHandle GetTypeDefOrRefHandle(EntityHandle baseTypeOrInterfaceHandle) + private static EntityHandle GetTypeDefOrRefHandle( + MetadataReader metadataReader, + EntityHandle baseTypeOrInterfaceHandle) { switch (baseTypeOrInterfaceHandle.Kind) { @@ -650,7 +665,7 @@ private EntityHandle GetTypeDefOrRefHandle(EntityHandle baseTypeOrInterfaceHandl return baseTypeOrInterfaceHandle; case HandleKind.TypeSpecification: return FirstEntityHandleProvider.Instance.GetTypeFromSpecification( - _metadataReader, (TypeSpecificationHandle)baseTypeOrInterfaceHandle); + metadataReader, (TypeSpecificationHandle)baseTypeOrInterfaceHandle); default: return default; } @@ -697,7 +712,7 @@ private void AddUnsortedNodes(ArrayBuilder unsortedNodes, MultiDictionary receiverTypeNameToMethodMap, MetadataNode parentNode, int parentIndex, - string fullyQualifiedContainerName) + string? fullyQualifiedContainerName) { foreach (var child in _parentToChildren[parentNode]) { @@ -727,7 +742,8 @@ private void AddUnsortedNodes(ArrayBuilder unsortedNodes, AddUnsortedNodes(unsortedNodes, receiverTypeNameToMethodMap, child, childIndex, Concat(fullyQualifiedContainerName, child.Name)); } - static string Concat(string containerName, string name) + [return: NotNullIfNotNull("containerName")] + static string? Concat(string? containerName, string name) { if (containerName == null) { @@ -746,7 +762,11 @@ static string Concat(string containerName, string name) private class MetadataNode { - public string Name { get; private set; } + /// + /// Represent this as non-null because that will be true when this is not in a pool and it is being used by + /// other services. + /// + public string Name { get; private set; } = null!; private static readonly ObjectPool s_pool = SharedPools.Default(); @@ -761,7 +781,7 @@ public static MetadataNode Allocate(string name) public static void Free(MetadataNode node) { Debug.Assert(node.Name != null); - node.Name = null; + node.Name = null!; s_pool.Free(node); } } From a588eca62dac249b7aa258294ea6638997ee907b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:02:38 -0700 Subject: [PATCH 14/42] In progress --- .../SymbolTree/SymbolTreeInfo_Metadata.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index 64b9d1f33fa88..69a9569ac8bab 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -762,14 +762,14 @@ private void AddUnsortedNodes(ArrayBuilder unsortedNodes, private class MetadataNode { + private static readonly ObjectPool s_pool = SharedPools.Default(); + /// /// Represent this as non-null because that will be true when this is not in a pool and it is being used by /// other services. /// public string Name { get; private set; } = null!; - private static readonly ObjectPool s_pool = SharedPools.Default(); - public static MetadataNode Allocate(string name) { var node = s_pool.Allocate(); @@ -793,7 +793,7 @@ private enum MetadataDefinitionKind Member, } - private struct MetadataDefinition + private readonly struct MetadataDefinition { public string Name { get; } public MetadataDefinitionKind Kind { get; } @@ -803,15 +803,21 @@ private struct MetadataDefinition /// public ParameterTypeInfo ReceiverTypeInfo { get; } - public NamespaceDefinition Namespace { get; private set; } - public TypeDefinition Type { get; private set; } + public NamespaceDefinition Namespace { get; } + public TypeDefinition Type { get; } - public MetadataDefinition(MetadataDefinitionKind kind, string name, ParameterTypeInfo receiverTypeInfo = default) - : this() + public MetadataDefinition( + MetadataDefinitionKind kind, + string name, + ParameterTypeInfo receiverTypeInfo = default, + NamespaceDefinition @namespace = default, + TypeDefinition type = default) { Kind = kind; Name = name; ReceiverTypeInfo = receiverTypeInfo; + Namespace = @namespace; + Type = type; } public static MetadataDefinition Create( @@ -820,21 +826,15 @@ public static MetadataDefinition Create( var definition = reader.GetNamespaceDefinition(namespaceHandle); return new MetadataDefinition( MetadataDefinitionKind.Namespace, - reader.GetString(definition.Name)) - { - Namespace = definition - }; + reader.GetString(definition.Name), + @namespace: definition); } public static MetadataDefinition Create( MetadataReader reader, TypeDefinition definition) { var typeName = GetMetadataNameWithoutBackticks(reader, definition.Name); - - return new MetadataDefinition(MetadataDefinitionKind.Type, typeName) - { - Type = definition - }; + return new MetadataDefinition(MetadataDefinitionKind.Type, typeName, type: definition); } } } From bb409f18ffd4de406db24df1f85cf6fde9b50780 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:06:31 -0700 Subject: [PATCH 15/42] NRT enable code --- .../Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs index c80c8ecd7b935..88be730e692c2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Diagnostics; using System.Reflection.Metadata; From a731465fcd45fceea82e21ac8e09f5d1c6da0bf4 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:09:40 -0700 Subject: [PATCH 16/42] NRT enable code --- .../SymbolTreeInfo.FirstEntityHandleProvider.cs | 12 +++++------- .../FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs | 10 +++++----- .../SymbolTree/SymbolTreeInfo_Metadata.cs | 2 +- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.FirstEntityHandleProvider.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.FirstEntityHandleProvider.cs index a8e9f56f097dc..4060cc3b7dd01 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.FirstEntityHandleProvider.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.FirstEntityHandleProvider.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; @@ -20,7 +18,7 @@ internal partial class SymbolTreeInfo /// are added. For example, for the type "X.Y.O`1.I`2, we will produce [X, Y, O, I] /// /// - private class FirstEntityHandleProvider : ISignatureTypeProvider + private class FirstEntityHandleProvider : ISignatureTypeProvider { public static readonly FirstEntityHandleProvider Instance = new(); @@ -30,10 +28,10 @@ public EntityHandle GetTypeFromSpecification(MetadataReader reader, TypeSpecific // instantiated generics). It will call back into us to get the first handle // for the type def or type ref that the specification starts with. var sigReader = reader.GetBlobReader(reader.GetTypeSpecification(handle).Signature); - return new SignatureDecoder(this, reader, genericContext: null).DecodeType(ref sigReader); + return new SignatureDecoder(this, reader, genericContext: null).DecodeType(ref sigReader); } - public EntityHandle GetTypeFromSpecification(MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind) => + public EntityHandle GetTypeFromSpecification(MetadataReader reader, object? genericContext, TypeSpecificationHandle handle, byte rawTypeKind) => GetTypeFromSpecification(reader, handle); public EntityHandle GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) => handle; @@ -55,8 +53,8 @@ public EntityHandle GetTypeFromSpecification(MetadataReader reader, object gener // just return the empty string. Similarly, as we never construct generics, // there is no need to provide anything for the generic parameter names. public EntityHandle GetFunctionPointerType(MethodSignature signature) => default; - public EntityHandle GetGenericMethodParameter(object genericContext, int index) => default; - public EntityHandle GetGenericTypeParameter(object genericContext, int index) => default; + public EntityHandle GetGenericMethodParameter(object? genericContext, int index) => default; + public EntityHandle GetGenericTypeParameter(object? genericContext, int index) => default; public EntityHandle GetPrimitiveType(PrimitiveTypeCode typeCode) => default; } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs index 88be730e692c2..ce67fb5932208 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.Node.cs @@ -136,7 +136,7 @@ public ExtensionMethodInfo(string fullyQualifiedContainerName, string name) } } - private sealed class ParameterTypeInfoProvider : ISignatureTypeProvider + private sealed class ParameterTypeInfoProvider : ISignatureTypeProvider { public static readonly ParameterTypeInfoProvider Instance = new(); @@ -168,10 +168,10 @@ public ParameterTypeInfo GetTypeFromReference(MetadataReader reader, TypeReferen return new ParameterTypeInfo(name, isComplex: false, isArray: false); } - public ParameterTypeInfo GetTypeFromSpecification(MetadataReader reader, object genericContext, TypeSpecificationHandle handle, byte rawTypeKind) + public ParameterTypeInfo GetTypeFromSpecification(MetadataReader reader, object? genericContext, TypeSpecificationHandle handle, byte rawTypeKind) { var sigReader = reader.GetBlobReader(reader.GetTypeSpecification(handle).Signature); - return new SignatureDecoder(Instance, reader, genericContext).DecodeType(ref sigReader); + return new SignatureDecoder(Instance, reader, genericContext).DecodeType(ref sigReader); } public ParameterTypeInfo GetArrayType(ParameterTypeInfo elementType, ArrayShape shape) => GetArrayTypeInfo(elementType); @@ -185,9 +185,9 @@ private static ParameterTypeInfo GetArrayTypeInfo(ParameterTypeInfo elementType) public ParameterTypeInfo GetFunctionPointerType(MethodSignature signature) => ComplexInfo; - public ParameterTypeInfo GetGenericMethodParameter(object genericContext, int index) => ComplexInfo; + public ParameterTypeInfo GetGenericMethodParameter(object? genericContext, int index) => ComplexInfo; - public ParameterTypeInfo GetGenericTypeParameter(object genericContext, int index) => ComplexInfo; + public ParameterTypeInfo GetGenericTypeParameter(object? genericContext, int index) => ComplexInfo; public ParameterTypeInfo GetModifiedType(ParameterTypeInfo modifier, ParameterTypeInfo unmodifiedType, bool isRequired) => ComplexInfo; diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs index 69a9569ac8bab..c6f2fef79644e 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo_Metadata.cs @@ -422,7 +422,7 @@ private void LookupMetadataDefinitions( { // Decode method signature to get the receiver type name (i.e. type name for the first parameter) var blob = metadataReader.GetBlobReader(method.Signature); - var decoder = new SignatureDecoder(ParameterTypeInfoProvider.Instance, metadataReader, genericContext: null!); + var decoder = new SignatureDecoder(ParameterTypeInfoProvider.Instance, metadataReader, genericContext: null); var signature = decoder.DecodeMethodSignature(ref blob); // It'd be good if we don't need to go through all parameters and make unnecessary allocations. From 26ba8f0bffc07fc24855636ff6cd8db7f002d7c1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:14:20 -0700 Subject: [PATCH 17/42] NRT enable code --- .../Core/Portable/FindSymbols/SearchQuery.cs | 6 ++---- .../Core/Portable/FindSymbols/SymbolCallerInfo.cs | 4 +--- .../SymbolTree/ISymbolTreeInfoCacheService.cs | 6 ++---- .../Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs | 10 +++++++--- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SearchQuery.cs b/src/Workspaces/Core/Portable/FindSymbols/SearchQuery.cs index b7b9fe03d4fa1..b69e756726380 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SearchQuery.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SearchQuery.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using Roslyn.Utilities; @@ -13,7 +11,7 @@ internal class SearchQuery : IDisposable { /// The name being searched for. Is null in the case of custom predicate searching.. But /// can be used for faster index based searching when it is available. - public readonly string Name; + public readonly string? Name; ///The kind of search this is. Faster index-based searching can be used if the /// SearchKind is not . @@ -22,7 +20,7 @@ internal class SearchQuery : IDisposable ///The predicate to fall back on if faster index searching is not possible. private readonly Func _predicate; - private readonly WordSimilarityChecker _wordSimilarityChecker; + private readonly WordSimilarityChecker? _wordSimilarityChecker; private SearchQuery(string name, SearchKind kind) { diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolCallerInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolCallerInfo.cs index 66fe2b9233077..c9c694affb235 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolCallerInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolCallerInfo.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; namespace Microsoft.CodeAnalysis.FindSymbols @@ -17,7 +15,7 @@ namespace Microsoft.CodeAnalysis.FindSymbols /// member that this symbol overrides, or through an interface member that this symbol /// implements will be considered 'indirect'. /// - public struct SymbolCallerInfo + public readonly struct SymbolCallerInfo { /// /// The symbol that is calling the symbol being called. diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs index c05a5b5774a8a..b93eecfcab6e8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/ISymbolTreeInfoCacheService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; @@ -15,11 +13,11 @@ internal interface ISymbolTreeInfoCacheService : IWorkspaceService /// /// Returns null if the info cannot be retrieved from the cache. /// - Task TryGetSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken); + Task TryGetSourceSymbolTreeInfoAsync(Project project, CancellationToken cancellationToken); /// /// Returns null if the info cannot be retrieved from the cache. /// - ValueTask TryGetMetadataSymbolTreeInfoAsync(Solution solution, PortableExecutableReference reference, CancellationToken cancellationToken); + ValueTask TryGetMetadataSymbolTreeInfoAsync(Solution solution, PortableExecutableReference reference, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs index a737a7de85f65..d61dd88901c96 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolTree/SymbolTreeInfo.cs @@ -169,11 +169,15 @@ private Task> FindCoreAsync( // If the query has a specific string provided, then call into the SymbolTreeInfo // helpers optimized for lookup based on an exact name. + + var queryName = query.Name; + Contract.ThrowIfNull(queryName); + return query.Kind switch { - SearchKind.Exact => this.FindAsync(lazyAssembly, query.Name, ignoreCase: false, cancellationToken: cancellationToken), - SearchKind.ExactIgnoreCase => this.FindAsync(lazyAssembly, query.Name, ignoreCase: true, cancellationToken: cancellationToken), - SearchKind.Fuzzy => this.FuzzyFindAsync(lazyAssembly, query.Name, cancellationToken), + SearchKind.Exact => this.FindAsync(lazyAssembly, queryName, ignoreCase: false, cancellationToken: cancellationToken), + SearchKind.ExactIgnoreCase => this.FindAsync(lazyAssembly, queryName, ignoreCase: true, cancellationToken: cancellationToken), + SearchKind.Fuzzy => this.FuzzyFindAsync(lazyAssembly, queryName, cancellationToken), _ => throw new InvalidOperationException(), }; } From 7823c42f54bc47d8b3f76f9bcc2fedcb952e2183 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:14:39 -0700 Subject: [PATCH 18/42] NRT enable code --- src/Workspaces/Core/Portable/FindSymbols/SearchKind.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SearchKind.cs b/src/Workspaces/Core/Portable/FindSymbols/SearchKind.cs index 0af0f9bfb9480..a9950c0eb7b04 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SearchKind.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SearchKind.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols From 027ccbab7f9494b7c3a0fefd413a65356c27dbcb Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:17:22 -0700 Subject: [PATCH 19/42] NRT enable code --- .../FindSymbols/FindReferences/FindReferencesProgress.cs | 2 -- .../Core/Portable/FindSymbols/IFindReferencesProgress.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs index 11d1d2e20c90e..d799784c3ef43 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesProgress.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.CodeAnalysis.FindSymbols { /// diff --git a/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs index f16c2ee8f3144..cc4060cc61ec5 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IFindReferencesProgress.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.CodeAnalysis.FindSymbols { /// From c4b4611d2e44a4279eb7adcc9265d48495edd33e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:18:09 -0700 Subject: [PATCH 20/42] NRT enable code --- .../FindReferences/NoOpStreamingFindReferencesProgress.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs index 3308d17f2bd31..64c9e5b7bda9b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/NoOpStreamingFindReferencesProgress.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -25,10 +23,6 @@ private NoOpStreamingFindReferencesProgress() { } -#pragma warning disable IDE0060 // Remove unused parameter - public static Task ReportProgressAsync(int current, int maximum) => Task.CompletedTask; -#pragma warning restore IDE0060 // Remove unused parameter - public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => default; public ValueTask OnStartedAsync(CancellationToken cancellationToken) => default; public ValueTask OnDefinitionFoundAsync(SymbolGroup group, CancellationToken cancellationToken) => default; From 784a98c307b9e8c6244b0abcab688f101a96582b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:19:14 -0700 Subject: [PATCH 21/42] NRT enable code --- .../FindReferences/MetadataUnifyingEquivalenceComparer.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingEquivalenceComparer.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingEquivalenceComparer.cs index fe682d13f68f0..0af7cdf738a8d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingEquivalenceComparer.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/MetadataUnifyingEquivalenceComparer.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -18,7 +16,7 @@ private MetadataUnifyingEquivalenceComparer() { } - public bool Equals(ISymbol x, ISymbol y) + public bool Equals(ISymbol? x, ISymbol? y) { // If either symbol is from source, then we must do stricter equality. Consider this: // From c526be85c5572bef3141a3eb7825d72d459c68ef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:22:16 -0700 Subject: [PATCH 22/42] NRT enable code --- .../FindReferences/IRemoteDependentTypeFinderService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/IRemoteDependentTypeFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/IRemoteDependentTypeFinderService.cs index 45a516c4dc98a..4e59868f38b6a 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/IRemoteDependentTypeFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/IRemoteDependentTypeFinderService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; From 7f39b56be3480d198545ffdbba2f4f0ed61ae80d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:25:51 -0700 Subject: [PATCH 23/42] NRT enable code --- .../FindReferences/StreamingFindReferencesProgress.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs index 8173504650cf8..ec17b61a5624b 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/StreamingFindReferencesProgress.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Threading; using System.Threading.Tasks; From 4009518a28dab0740ccf18b02ae97fa2c0e360ef Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:25:58 -0700 Subject: [PATCH 24/42] NRT enable code --- .../Portable/FindSymbols/FindReferences/DependentTypesKind.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypesKind.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypesKind.cs index a9ec879fd6b5b..0a87cbfc930c2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypesKind.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/DependentTypesKind.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - namespace Microsoft.CodeAnalysis.FindSymbols { internal enum DependentTypesKind From 1fc6e09b45c92a0e1b8d4a97db4a8dc41d68815d Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:27:13 -0700 Subject: [PATCH 25/42] NRT enable code --- .../Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs index bb4d13878f077..2b24a0df2171f 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/BaseTypeFinder.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; From aebd377caea03aef7ddd97b552b7d6160c926f9b Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:30:20 -0700 Subject: [PATCH 26/42] NRT enable code --- .../FindLiterals/FindLiteralsSearchEngine.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs index 159d2fa0884bc..de39d1e3ba768 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindLiterals/FindLiteralsSearchEngine.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -13,6 +11,7 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { @@ -31,7 +30,7 @@ private enum SearchKind private readonly IStreamingProgressTracker _progressTracker; private readonly object _value; - private readonly string _stringValue; + private readonly string? _stringValue; private readonly long _longValue; private readonly SearchKind _searchKind; @@ -119,8 +118,10 @@ private async Task ProcessDocumentWorkerAsync(Document document, CancellationTok var index = await SyntaxTreeIndex.GetIndexAsync( document, cancellationToken).ConfigureAwait(false); + Contract.ThrowIfNull(index); if (_searchKind == SearchKind.StringLiterals) { + Contract.ThrowIfNull(_stringValue); if (index.ProbablyContainsStringValue(_stringValue)) { await SearchDocumentAsync(document, cancellationToken).ConfigureAwait(false); @@ -135,16 +136,14 @@ private async Task ProcessDocumentWorkerAsync(Document document, CancellationTok private async Task SearchDocumentAsync(Document document, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); - var syntaxFacts = document.GetLanguageService(); - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var matches = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var matches); ProcessNode(syntaxFacts, root, matches, cancellationToken); foreach (var token in matches) - { await _progress.OnReferenceFoundAsync(document, token.Span, cancellationToken).ConfigureAwait(false); - } } private void ProcessNode( @@ -156,7 +155,7 @@ private void ProcessNode( { if (child.IsNode) { - ProcessNode(syntaxFacts, child.AsNode(), matches, cancellationToken); + ProcessNode(syntaxFacts, child.AsNode()!, matches, cancellationToken); } else { From a7322b38274e7c4219ac6990eece30f49f894c6f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:30:46 -0700 Subject: [PATCH 27/42] NRT enable code --- src/Workspaces/Core/Portable/FindSymbols/Extensions.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/Extensions.cs b/src/Workspaces/Core/Portable/FindSymbols/Extensions.cs index f7f027d00afc6..751a93bdeabd2 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Extensions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Extensions.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; From 08cb9e5906a198ace2a6e46e3592a132f20a8d70 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:35:42 -0700 Subject: [PATCH 28/42] NRT enable code --- ...vigateToSearchService.CachedDocumentSearch.cs | 2 +- .../DeclarationFinder_SourceDeclarations.cs | 13 +++---------- .../Portable/PatternMatching/PatternMatcher.cs | 16 +++++++--------- .../PatternMatching/PatternMatcherExtensions.cs | 5 +++-- 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs index 868736a0b90bb..e548a495234a8 100644 --- a/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs +++ b/src/Features/Core/Portable/NavigateTo/AbstractNavigateToSearchService.CachedDocumentSearch.cs @@ -114,7 +114,7 @@ await SearchCachedDocumentsInCurrentProcessAsync( private static async Task SearchCachedDocumentsInCurrentProcessAsync( IChecksummedPersistentStorageService storageService, string patternName, - string patternContainer, + string? patternContainer, DeclaredSymbolInfoKindSet kinds, Func onItemFound, ImmutableArray documentKeys, diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs index 6421c5f5c25c1..bf39ca7e27b2d 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs @@ -2,16 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols { @@ -214,9 +212,6 @@ private static async Task> FindSourceDeclarationsWithPat // we'll check if the full name matches the full pattern. var (namePart, containerPart) = PatternMatcher.GetNameAndContainer(pattern); - var dotIndex = pattern.LastIndexOf('.'); - var isDottedPattern = dotIndex >= 0; - // If we don't have a dot in the pattern, just make a pattern matcher for the entire // pattern they passed in. Otherwise, make a pattern matcher just for the part after // the dot. @@ -226,7 +221,7 @@ private static async Task> FindSourceDeclarationsWithPat var symbolAndProjectIds = await searchAsync(query).ConfigureAwait(false); if (symbolAndProjectIds.Length == 0 || - !isDottedPattern) + containerPart == null) { // If it wasn't a dotted pattern, or we didn't get anything back, then we're done. // We can just return whatever set of results we got so far. @@ -257,13 +252,11 @@ internal static Task> FindSourceDeclarationsWithPatternI query => SymbolFinder.FindSourceDeclarationsWithCustomQueryAsync(project, query, criteria, cancellationToken)); } - private static string GetContainer(ISymbol symbol) + private static string? GetContainer(ISymbol symbol) { var container = symbol.ContainingSymbol; if (container == null) - { return null; - } return container.ToDisplayString(DottedNameFormat); } diff --git a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs index b5d864eb217f9..36c33c465e7be 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcher.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Immutable; using System.Diagnostics; @@ -66,7 +64,7 @@ public virtual void Dispose() public static PatternMatcher CreatePatternMatcher( string pattern, - CultureInfo culture = null, + CultureInfo? culture = null, bool includeMatchedSpans = false, bool allowFuzzyMatching = false) { @@ -76,7 +74,7 @@ public static PatternMatcher CreatePatternMatcher( public static PatternMatcher CreateContainerPatternMatcher( string[] patternParts, char[] containerSplitCharacters, - CultureInfo culture = null, + CultureInfo? culture = null, bool allowFuzzyMatching = false) { return new ContainerPatternMatcher( @@ -85,7 +83,7 @@ public static PatternMatcher CreateContainerPatternMatcher( public static PatternMatcher CreateDotSeparatedContainerMatcher( string pattern, - CultureInfo culture = null, + CultureInfo? culture = null, bool allowFuzzyMatching = false) { return CreateContainerPatternMatcher( @@ -93,18 +91,18 @@ public static PatternMatcher CreateDotSeparatedContainerMatcher( s_dotCharacterArray, culture, allowFuzzyMatching); } - internal static (string name, string containerOpt) GetNameAndContainer(string pattern) + internal static (string name, string? containerOpt) GetNameAndContainer(string pattern) { var dotIndex = pattern.LastIndexOf('.'); var containsDots = dotIndex >= 0; return containsDots - ? (name: pattern.Substring(dotIndex + 1), containerOpt: pattern.Substring(0, dotIndex)) + ? (name: pattern[(dotIndex + 1)..], containerOpt: pattern[..dotIndex]) : (name: pattern, containerOpt: null); } - public abstract bool AddMatches(string candidate, ref TemporaryArray matches); + public abstract bool AddMatches(string? candidate, ref TemporaryArray matches); - private bool SkipMatch(string candidate) + private bool SkipMatch(string? candidate) => _invalidPattern || string.IsNullOrWhiteSpace(candidate); private static bool ContainsUpperCaseLetter(string pattern) diff --git a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcherExtensions.cs b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcherExtensions.cs index f6d3ce848895b..949047b6ed66b 100644 --- a/src/Workspaces/Core/Portable/PatternMatching/PatternMatcherExtensions.cs +++ b/src/Workspaces/Core/Portable/PatternMatching/PatternMatcherExtensions.cs @@ -2,20 +2,21 @@ // 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.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.Shared.Collections; namespace Microsoft.CodeAnalysis.PatternMatching { internal static class PatternMatcherExtensions { - public static PatternMatch? GetFirstMatch(this PatternMatcher matcher, string candidate) + public static PatternMatch? GetFirstMatch(this PatternMatcher matcher, string? candidate) { using var matches = TemporaryArray.Empty; matcher.AddMatches(candidate, ref matches.AsRef()); return matches.Count > 0 ? matches[0] : null; } - public static bool Matches(this PatternMatcher matcher, string candidate) + public static bool Matches(this PatternMatcher matcher, [NotNullWhen(true)] string? candidate) => matcher.GetFirstMatch(candidate) != null; } } From e8d65ef21dd3435b43ec9e8aa7ca09b51c2d36b1 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:43:59 -0700 Subject: [PATCH 29/42] NRT enable code --- .../Declarations/DeclarationFinder.cs | 19 ++++++++++--------- .../DeclarationFinder_SourceDeclarations.cs | 12 ++++++------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs index 796e5747e837f..f8e232f60a3c8 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Immutable; using System.Linq; @@ -36,8 +34,8 @@ private static async Task AddCompilationDeclarationsWithNormalQueryAsync( SearchQuery query, SymbolFilter filter, ArrayBuilder list, - Compilation startingCompilation, - IAssemblySymbol startingAssembly, + Compilation? startingCompilation, + IAssemblySymbol? startingAssembly, CancellationToken cancellationToken) { if (!project.SupportsCompilation) @@ -47,7 +45,7 @@ private static async Task AddCompilationDeclarationsWithNormalQueryAsync( using (Logger.LogBlock(FunctionId.SymbolFinder_Project_AddDeclarationsAsync, cancellationToken)) { - var syntaxFacts = project.LanguageServices.GetService(); + var syntaxFacts = project.GetRequiredLanguageService(); // If this is an exact query, we can speed things up by just calling into the // compilation entrypoints that take a string directly. @@ -64,16 +62,16 @@ private static async Task AddCompilationDeclarationsWithNormalQueryAsync( // exact name search, then we will run the query's predicate over every DeclaredSymbolInfo stored in // the doc. var containsSymbol = isExactNameSearch - ? await project.ContainsSymbolsWithNameAsync(query.Name, cancellationToken).ConfigureAwait(false) + ? await project.ContainsSymbolsWithNameAsync(query.Name!, cancellationToken).ConfigureAwait(false) : await project.ContainsSymbolsWithNameAsync(query.GetPredicate(), filter, cancellationToken).ConfigureAwait(false); if (!containsSymbol) return; - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); var symbols = isExactNameSearch - ? compilation.GetSymbolsWithName(query.Name, filter, cancellationToken) + ? compilation.GetSymbolsWithName(query.Name!, filter, cancellationToken) : compilation.GetSymbolsWithName(query.GetPredicate(), filter, cancellationToken); var symbolsWithName = symbols.ToImmutableArray(); @@ -105,7 +103,10 @@ private static async Task AddMetadataDeclarationsWithNormalQueryAsync( if (referenceOpt != null) { var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( - project.Solution, referenceOpt, loadOnly: false, cancellationToken: cancellationToken).ConfigureAwait(false); + project.Solution, referenceOpt, loadOnly: false, cancellationToken).ConfigureAwait(false); + + // We must have an index since we passed in loadOnly: false here. + Contract.ThrowIfNull(info); var symbols = await info.FindAsync( query, assembly, filter, cancellationToken).ConfigureAwait(false); diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs index bf39ca7e27b2d..542c7f6693198 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.PatternMatching; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FindSymbols @@ -179,9 +180,8 @@ internal static async Task> FindSourceDeclarationsWithNo using var query = SearchQuery.Create(name, ignoreCase); var result = ArrayBuilder.GetInstance(); - foreach (var projectId in solution.ProjectIds) + foreach (var project in solution.Projects) { - var project = solution.GetProject(projectId); await AddCompilationDeclarationsWithNormalQueryAsync( project, query, criteria, result, cancellationToken).ConfigureAwait(false); } @@ -192,13 +192,13 @@ await AddCompilationDeclarationsWithNormalQueryAsync( internal static async Task> FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync( Project project, string name, bool ignoreCase, SymbolFilter filter, CancellationToken cancellationToken) { - var list = ArrayBuilder.GetInstance(); - + using var _ = ArrayBuilder.GetInstance(out var result); using var query = SearchQuery.Create(name, ignoreCase); await AddCompilationDeclarationsWithNormalQueryAsync( - project, query, filter, list, cancellationToken).ConfigureAwait(false); - return list.ToImmutableAndFree(); + project, query, filter, result, cancellationToken).ConfigureAwait(false); + + return result.ToImmutable(); } private static async Task> FindSourceDeclarationsWithPatternInCurrentProcessAsync( From edc55accb156e680b31688267a9d5cf7be74ae1c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:44:42 -0700 Subject: [PATCH 30/42] NRT enable code --- .../Declarations/DeclarationFinder_SourceDeclarations.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs index 542c7f6693198..8ccc648c416ab 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_SourceDeclarations.cs @@ -179,14 +179,14 @@ internal static async Task> FindSourceDeclarationsWithNo { using var query = SearchQuery.Create(name, ignoreCase); - var result = ArrayBuilder.GetInstance(); + using var _ = ArrayBuilder.GetInstance(out var result); foreach (var project in solution.Projects) { await AddCompilationDeclarationsWithNormalQueryAsync( project, query, criteria, result, cancellationToken).ConfigureAwait(false); } - return result.ToImmutableAndFree(); + return result.ToImmutable(); } internal static async Task> FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync( From 9c85aff2b2f43f9a396efd01f07fea37007daf86 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:47:42 -0700 Subject: [PATCH 31/42] NRT enable code --- .../Declarations/DeclarationFinder.cs | 15 +++++++----- .../DeclarationFinder_AllDeclarations.cs | 24 ++++++++++--------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs index f8e232f60a3c8..a984cd82e69fb 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder.cs @@ -90,8 +90,12 @@ private static async Task AddCompilationDeclarationsWithNormalQueryAsync( } private static async Task AddMetadataDeclarationsWithNormalQueryAsync( - Project project, IAssemblySymbol assembly, PortableExecutableReference referenceOpt, - SearchQuery query, SymbolFilter filter, ArrayBuilder list, + Project project, + IAssemblySymbol assembly, + PortableExecutableReference? reference, + SearchQuery query, + SymbolFilter filter, + ArrayBuilder list, CancellationToken cancellationToken) { // All entrypoints to this function are Find functions that are only searching @@ -100,16 +104,15 @@ private static async Task AddMetadataDeclarationsWithNormalQueryAsync( using (Logger.LogBlock(FunctionId.SymbolFinder_Assembly_AddDeclarationsAsync, cancellationToken)) { - if (referenceOpt != null) + if (reference != null) { var info = await SymbolTreeInfo.GetInfoForMetadataReferenceAsync( - project.Solution, referenceOpt, loadOnly: false, cancellationToken).ConfigureAwait(false); + project.Solution, reference, loadOnly: false, cancellationToken).ConfigureAwait(false); // We must have an index since we passed in loadOnly: false here. Contract.ThrowIfNull(info); - var symbols = await info.FindAsync( - query, assembly, filter, cancellationToken).ConfigureAwait(false); + var symbols = await info.FindAsync(query, assembly, filter, cancellationToken).ConfigureAwait(false); list.AddRange(symbols); } } diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs index c3dce2134a243..1bc58ccfabdb6 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -63,15 +61,16 @@ public static async Task> FindAllDeclarationsWithNormalQ internal static async Task> FindAllDeclarationsWithNormalQueryInCurrentProcessAsync( Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken) { - var list = ArrayBuilder.GetInstance(); + using var _1 = ArrayBuilder.GetInstance(out var buffer); + using var _2 = ArrayBuilder.GetInstance(out var result); if (project.SupportsCompilation) { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); // get declarations from the compilation's assembly await AddCompilationDeclarationsWithNormalQueryAsync( - project, query, criteria, list, cancellationToken).ConfigureAwait(false); + project, query, criteria, buffer, cancellationToken).ConfigureAwait(false); // get declarations from directly referenced projects and metadata foreach (var assembly in compilation.GetReferencedAssemblySymbols()) @@ -80,30 +79,33 @@ await AddCompilationDeclarationsWithNormalQueryAsync( if (assemblyProject != null) { await AddCompilationDeclarationsWithNormalQueryAsync( - assemblyProject, query, criteria, list, + assemblyProject, query, criteria, buffer, compilation, assembly, cancellationToken).ConfigureAwait(false); } else { await AddMetadataDeclarationsWithNormalQueryAsync( project, assembly, compilation.GetMetadataReference(assembly) as PortableExecutableReference, - query, criteria, list, cancellationToken).ConfigureAwait(false); + query, criteria, buffer, cancellationToken).ConfigureAwait(false); } } // Make certain all namespace symbols returned by API are from the compilation // for the passed in project. - for (var i = 0; i < list.Count; i++) + foreach (var symbol in buffer) { - var symbol = list[i]; if (symbol is INamespaceSymbol ns) { - list[i] = compilation.GetCompilationNamespace(ns); + result.AddIfNotNull(compilation.GetCompilationNamespace(ns)); + } + else + { + result.Add(symbol); } } } - return list.ToImmutableAndFree(); + return result.ToImmutable(); } private static async Task> RehydrateAsync( From 726a73a6dcd071fdf19dd2c8e67690b2683c628e Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:51:16 -0700 Subject: [PATCH 32/42] NRT enable code --- .../Declarations/DeclarationFinder_AllDeclarations.cs | 7 ++----- .../Portable/FindSymbols/IRemoteSymbolFinderService.cs | 2 -- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs index 1bc58ccfabdb6..896b7613e04a1 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/Declarations/DeclarationFinder_AllDeclarations.cs @@ -27,14 +27,11 @@ public static async Task> FindAllDeclarationsWithNormalQ Contract.ThrowIfTrue(query.Kind == SearchKind.Custom, "Custom queries are not supported in this API"); if (project == null) - { throw new ArgumentNullException(nameof(project)); - } - if (query.Name != null && string.IsNullOrWhiteSpace(query.Name)) - { + Contract.ThrowIfNull(query.Name); + if (string.IsNullOrWhiteSpace(query.Name)) return ImmutableArray.Empty; - } var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false); if (client != null) diff --git a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs index 3c2dd216c7f5e..4a9eac28592b0 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/IRemoteSymbolFinderService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Immutable; using System.Threading; From 78a4d3006ea3bcf38c8b23c778384b070d3592bf Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 11:53:56 -0700 Subject: [PATCH 33/42] NRT enable code --- .../Portable/FindSymbols/ReferenceLocationExtensions.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs b/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs index be877c762a146..15ca8fee89438 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/ReferenceLocationExtensions.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -30,12 +28,12 @@ public static async Task>> FindReferencingSym var project = projectGroup.Key; if (project.SupportsCompilation) { - var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); + var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false); foreach (var documentGroup in projectGroup) { var document = documentGroup.Key; - var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); AddSymbols(semanticModel, documentGroup, result); } @@ -68,7 +66,7 @@ private static void AddSymbols( } } - private static ISymbol GetEnclosingMethodOrPropertyOrField( + private static ISymbol? GetEnclosingMethodOrPropertyOrField( SemanticModel semanticModel, ReferenceLocation reference) { From 66f6e8bdf47c204edfd43c3f726636bb3566ffba Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 12:00:01 -0700 Subject: [PATCH 34/42] NRT enable code --- .../FindReferences/FindReferenceCache.cs | 42 +++++++------------ .../FindReferencesSearchEngine.cs | 2 +- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs index 307e6066438da..a274ce2d39a58 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferenceCache.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -36,19 +34,15 @@ public static SymbolInfo GetSymbolInfo(SemanticModel model, SyntaxNode node, Can return nodeCache.GetOrAdd(node, static (n, arg) => arg.model.GetSymbolInfo(n, arg.cancellationToken), (model, cancellationToken)); } - public static IAliasSymbol GetAliasInfo( + public static IAliasSymbol? GetAliasInfo( ISemanticFactsService semanticFacts, SemanticModel model, SyntaxToken token, CancellationToken cancellationToken) { if (semanticFacts == null) - { - return model.GetAliasInfo(token.Parent, cancellationToken); - } + return model.GetAliasInfo(token.GetRequiredParent(), cancellationToken); var entry = GetCachedEntry(model); if (entry == null) - { - return model.GetAliasInfo(token.Parent, cancellationToken); - } + return model.GetAliasInfo(token.GetRequiredParent(), cancellationToken); if (entry.AliasNameSet == null) { @@ -57,9 +51,7 @@ public static IAliasSymbol GetAliasInfo( } if (entry.AliasNameSet.Contains(token.ValueText)) - { - return model.GetAliasInfo(token.Parent, cancellationToken); - } + return model.GetAliasInfo(token.GetRequiredParent(), cancellationToken); return null; } @@ -68,7 +60,7 @@ public static ImmutableArray GetIdentifierOrGlobalNamespaceTokensWi ISyntaxFactsService syntaxFacts, SemanticModel model, SyntaxNode root, - SourceText sourceText, + SourceText? sourceText, string text, CancellationToken cancellationToken) { @@ -76,9 +68,7 @@ public static ImmutableArray GetIdentifierOrGlobalNamespaceTokensWi var entry = GetCachedEntry(model); if (entry == null) - { return GetIdentifierOrGlobalNamespaceTokensWithText(syntaxFacts, root, sourceText, normalized, cancellationToken); - } return entry.IdentifierCache.GetOrAdd(normalized, key => GetIdentifierOrGlobalNamespaceTokensWithText( @@ -87,8 +77,11 @@ public static ImmutableArray GetIdentifierOrGlobalNamespaceTokensWi [PerformanceSensitive("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1224834", AllowCaptures = false)] private static ImmutableArray GetIdentifierOrGlobalNamespaceTokensWithText( - ISyntaxFactsService syntaxFacts, SyntaxNode root, SourceText sourceText, - string text, CancellationToken cancellationToken) + ISyntaxFactsService syntaxFacts, + SyntaxNode root, + SourceText? sourceText, + string text, + CancellationToken cancellationToken) { if (sourceText != null) { @@ -182,18 +175,13 @@ private static List GetConstructorInitializerTokens( return initializers; } - private static ConcurrentDictionary GetNodeCache(SemanticModel model) + private static ConcurrentDictionary? GetNodeCache(SemanticModel model) { var entry = GetCachedEntry(model); - if (entry == null) - { - return null; - } - - return entry.SymbolInfoCache; + return entry?.SymbolInfoCache; } - private static Entry GetCachedEntry(SemanticModel model) + private static Entry? GetCachedEntry(SemanticModel model) { using (s_gate.DisposableRead()) { @@ -244,8 +232,8 @@ public static void Stop(SemanticModel model) private class Entry { public int Count; - public ImmutableHashSet AliasNameSet; - public List ConstructorInitializerCache; + public ImmutableHashSet? AliasNameSet; + public List? ConstructorInitializerCache; public readonly ConcurrentDictionary> IdentifierCache = new(); public readonly ConcurrentDictionary SymbolInfoCache = new(); diff --git a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs index 6d6d06211e299..7f2084a096386 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/FindReferences/FindReferencesSearchEngine.cs @@ -284,7 +284,7 @@ private async Task ProcessDocumentAsync( } finally { - FindReferenceCache.Stop(model); + FindReferenceCache.Stop(model!); await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); } From e945e2e6f351e03caa57efca8824f8712bd48bc0 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 15:05:48 -0700 Subject: [PATCH 35/42] Fix for generated docs --- .../SymbolFinder.FindReferencesServerCallback.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs index 1feb74ace7c1d..1c26f07869644 100644 --- a/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs +++ b/src/Workspaces/Core/Portable/FindSymbols/SymbolFinder.FindReferencesServerCallback.cs @@ -49,16 +49,16 @@ public ValueTask OnStartedAsync(CancellationToken cancellationToken) public ValueTask OnCompletedAsync(CancellationToken cancellationToken) => _progress.OnCompletedAsync(cancellationToken); - public ValueTask OnFindInDocumentStartedAsync(DocumentId documentId, CancellationToken cancellationToken) + public async ValueTask OnFindInDocumentStartedAsync(DocumentId documentId, CancellationToken cancellationToken) { - var document = _solution.GetRequiredDocument(documentId); - return _progress.OnFindInDocumentStartedAsync(document, cancellationToken); + var document = await _solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + await _progress.OnFindInDocumentStartedAsync(document, cancellationToken).ConfigureAwait(false); } - public ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId, CancellationToken cancellationToken) + public async ValueTask OnFindInDocumentCompletedAsync(DocumentId documentId, CancellationToken cancellationToken) { - var document = _solution.GetRequiredDocument(documentId); - return _progress.OnFindInDocumentCompletedAsync(document, cancellationToken); + var document = await _solution.GetRequiredDocumentAsync(documentId, includeSourceGenerated: true, cancellationToken).ConfigureAwait(false); + await _progress.OnFindInDocumentCompletedAsync(document, cancellationToken).ConfigureAwait(false); } public async ValueTask OnDefinitionFoundAsync(SerializableSymbolGroup dehydrated, CancellationToken cancellationToken) From 68b5bc7a0755864ce169bb52a8e64b09e840c87a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Wed, 15 Jun 2022 17:54:12 -0700 Subject: [PATCH 36/42] Convert connection lost exceptions to cancellations to avoid overreporting NFE (#61792) * Convert connection lost exceptions to cancellations to avoid overreporting NFE * Typo and comment * Feedback * Refactoring --- .../Portable/InternalUtilities/FatalError.cs | 16 ++- src/Workspaces/Remote/Core/RemoteCallback.cs | 50 +++++-- .../Host/RemoteWorkspace.SolutionCreator.cs | 131 +++++++++--------- .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 7 +- .../ServiceHub/Host/SolutionAssetSource.cs | 17 ++- 5 files changed, 135 insertions(+), 86 deletions(-) diff --git a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs index 2f3390cc0cd62..328e663df7978 100644 --- a/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs +++ b/src/Compilers/Core/Portable/InternalUtilities/FatalError.cs @@ -11,6 +11,18 @@ namespace Microsoft.CodeAnalysis.ErrorReporting { + /// + /// Thrown when async code must cancel the current execution but does not have access to the of the passed to the code. + /// Should be used in very rare cases where the is out of our control (e.g. owned but not exposed by JSON RPC in certain call-back scenarios). + /// + internal sealed class OperationCanceledIgnoringCallerTokenException : OperationCanceledException + { + public OperationCanceledIgnoringCallerTokenException(Exception innerException) + : base(innerException.Message, innerException) + { + } + } + internal static class FatalError { public delegate void ErrorReporterHandler(Exception exception, ErrorSeverity severity, bool forceDump); @@ -124,7 +136,7 @@ public static bool ReportAndPropagateUnlessCanceled(Exception exception, ErrorSe [DebuggerHidden] public static bool ReportAndPropagateUnlessCanceled(Exception exception, CancellationToken contextCancellationToken, ErrorSeverity severity = ErrorSeverity.Uncategorized) { - if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken)) + if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken) || exception is OperationCanceledIgnoringCallerTokenException) { return false; } @@ -200,7 +212,7 @@ public static bool ReportAndCatchUnlessCanceled(Exception exception, ErrorSeveri [DebuggerHidden] public static bool ReportAndCatchUnlessCanceled(Exception exception, CancellationToken contextCancellationToken, ErrorSeverity severity = ErrorSeverity.Uncategorized) { - if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken)) + if (ExceptionUtilities.IsCurrentOperationBeingCancelled(exception, contextCancellationToken) || exception is OperationCanceledIgnoringCallerTokenException) { return false; } diff --git a/src/Workspaces/Remote/Core/RemoteCallback.cs b/src/Workspaces/Remote/Core/RemoteCallback.cs index 5444ee902333c..5c9cdadd97a99 100644 --- a/src/Workspaces/Remote/Core/RemoteCallback.cs +++ b/src/Workspaces/Remote/Core/RemoteCallback.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.ServiceHub.Framework; using Roslyn.Utilities; using StreamJsonRpc; @@ -17,8 +18,6 @@ namespace Microsoft.CodeAnalysis.Remote /// Wraps calls from a remote brokered service back to the client or to an in-proc brokered service. /// The purpose of this type is to handle exceptions thrown by the underlying remoting infrastructure /// in manner that's compatible with our exception handling policies. - /// - /// TODO: This wrapper might not be needed once https://github.com/microsoft/vs-streamjsonrpc/issues/246 is fixed. /// internal readonly struct RemoteCallback where T : class @@ -30,6 +29,36 @@ public RemoteCallback(T callback) _callback = callback; } + /// + /// Use to perform a callback from ServiceHub process to an arbitrary brokered service hosted in the original process (usually devenv). + /// + public static async ValueTask InvokeServiceAsync( + ServiceBrokerClient client, + ServiceRpcDescriptor serviceDescriptor, + Func, CancellationToken, ValueTask> invocation, + CancellationToken cancellationToken) + { + ServiceBrokerClient.Rental rental; + try + { + rental = await client.GetProxyAsync(serviceDescriptor, cancellationToken).ConfigureAwait(false); + } + catch (ObjectDisposedException e) + { + // When a connection is dropped ServiceHub's ServiceManager disposes the brokered service, which in turn disposes the ServiceBrokerClient. + cancellationToken.ThrowIfCancellationRequested(); + throw new OperationCanceledIgnoringCallerTokenException(e); + } + + Contract.ThrowIfNull(rental.Proxy); + var callback = new RemoteCallback(rental.Proxy); + + return await invocation(callback, cancellationToken).ConfigureAwait(false); + } + + /// + /// Invokes API on the callback object hosted in the original process (usually devenv) associated with the currently executing brokered service hosted in ServiceHub process. + /// public async ValueTask InvokeAsync(Func invocation, CancellationToken cancellationToken) { try @@ -42,6 +71,9 @@ public async ValueTask InvokeAsync(Func invocat } } + /// + /// Invokes API on the callback object hosted in the original process (usually devenv) associated with the currently executing brokered service hosted in ServiceHub process. + /// public async ValueTask InvokeAsync(Func> invocation, CancellationToken cancellationToken) { try @@ -55,7 +87,8 @@ public async ValueTask InvokeAsync(Func - /// Invokes a remote API that streams results back to the caller. + /// Invokes API on the callback object hosted in the original process (usually devenv) associated with the currently executing brokered service hosted in ServiceHub process. + /// The API streams results back to the caller. /// /// public async ValueTask InvokeAsync( @@ -100,12 +133,9 @@ private static bool ReportUnexpectedException(Exception exception, CancellationT return FatalError.ReportAndCatch(exception); } - // When a connection is dropped and CancelLocallyInvokedMethodsWhenConnectionIsClosed is - // set ConnectionLostException should not be thrown. Instead the cancellation token should be - // signaled and OperationCancelledException should be thrown. - // Seems to not work in all cases currently, so we need to cancel ourselves (bug https://github.com/microsoft/vs-streamjsonrpc/issues/551). - // Once this issue is fixed we can remov this if statement and fall back to reporting NFW - // as any observation of ConnectionLostException indicates a bug (e.g. https://github.com/microsoft/vs-streamjsonrpc/issues/549). + // When a connection is dropped we can see ConnectionLostException even though CancelLocallyInvokedMethodsWhenConnectionIsClosed is set. + // That's because there might be a delay between the JsonRpc detecting the disconnect and the call attempting to send a message. + // Catch the ConnectionLostException exception here and convert it to OperationCanceledException. if (exception is ConnectionLostException) { return true; @@ -121,7 +151,7 @@ private static Exception OnUnexpectedException(Exception exception, Cancellation if (exception is ConnectionLostException) { - throw new OperationCanceledException(exception.Message, exception); + throw new OperationCanceledIgnoringCallerTokenException(exception); } // If this is hit the cancellation token passed to the service implementation did not use the correct token, diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs index 5a57598030c04..5485e3977233d 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.SolutionCreator.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Remote { @@ -32,26 +33,24 @@ private readonly struct SolutionCreator private readonly AssetProvider _assetProvider; private readonly Solution _baseSolution; - private readonly CancellationToken _cancellationToken; - public SolutionCreator(HostServices hostServices, AssetProvider assetService, Solution baseSolution, CancellationToken cancellationToken) + public SolutionCreator(HostServices hostServices, AssetProvider assetService, Solution baseSolution) { _hostServices = hostServices; _assetProvider = assetService; _baseSolution = baseSolution; - _cancellationToken = cancellationToken; } - public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum) + public async Task IsIncrementalUpdateAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { - var newSolutionChecksums = await _assetProvider.GetAssetAsync(newSolutionChecksum, _cancellationToken).ConfigureAwait(false); - var newSolutionInfo = await _assetProvider.GetAssetAsync(newSolutionChecksums.Attributes, _cancellationToken).ConfigureAwait(false); + var newSolutionChecksums = await _assetProvider.GetAssetAsync(newSolutionChecksum, cancellationToken).ConfigureAwait(false); + var newSolutionInfo = await _assetProvider.GetAssetAsync(newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); // if either solution id or file path changed, then we consider it as new solution return _baseSolution.Id == newSolutionInfo.Id && _baseSolution.FilePath == newSolutionInfo.FilePath; } - public async Task CreateSolutionAsync(Checksum newSolutionChecksum) + public async Task CreateSolutionAsync(Checksum newSolutionChecksum, CancellationToken cancellationToken) { try { @@ -61,12 +60,12 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum) // if needed again later. solution = solution.WithoutFrozenSourceGeneratedDocuments(); - var oldSolutionChecksums = await solution.State.GetStateChecksumsAsync(_cancellationToken).ConfigureAwait(false); - var newSolutionChecksums = await _assetProvider.GetAssetAsync(newSolutionChecksum, _cancellationToken).ConfigureAwait(false); + var oldSolutionChecksums = await solution.State.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); + var newSolutionChecksums = await _assetProvider.GetAssetAsync(newSolutionChecksum, cancellationToken).ConfigureAwait(false); if (oldSolutionChecksums.Attributes != newSolutionChecksums.Attributes) { - var newSolutionInfo = await _assetProvider.GetAssetAsync(newSolutionChecksums.Attributes, _cancellationToken).ConfigureAwait(false); + var newSolutionInfo = await _assetProvider.GetAssetAsync(newSolutionChecksums.Attributes, cancellationToken).ConfigureAwait(false); // if either id or file path has changed, then this is not update Contract.ThrowIfFalse(solution.Id == newSolutionInfo.Id && solution.FilePath == newSolutionInfo.FilePath); @@ -74,26 +73,26 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum) if (oldSolutionChecksums.Projects.Checksum != newSolutionChecksums.Projects.Checksum) { - solution = await UpdateProjectsAsync(solution, oldSolutionChecksums.Projects, newSolutionChecksums.Projects).ConfigureAwait(false); + solution = await UpdateProjectsAsync(solution, oldSolutionChecksums.Projects, newSolutionChecksums.Projects, cancellationToken).ConfigureAwait(false); } if (oldSolutionChecksums.AnalyzerReferences.Checksum != newSolutionChecksums.AnalyzerReferences.Checksum) { solution = solution.WithAnalyzerReferences(await _assetProvider.CreateCollectionAsync( - newSolutionChecksums.AnalyzerReferences, _cancellationToken).ConfigureAwait(false)); + newSolutionChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } if (newSolutionChecksums.FrozenSourceGeneratedDocumentIdentity != Checksum.Null && newSolutionChecksums.FrozenSourceGeneratedDocumentText != Checksum.Null) { - var identity = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentIdentity, _cancellationToken).ConfigureAwait(false); - var serializableSourceText = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentText, _cancellationToken).ConfigureAwait(false); - var sourceText = await serializableSourceText.GetTextAsync(_cancellationToken).ConfigureAwait(false); + var identity = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentIdentity, cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync(newSolutionChecksums.FrozenSourceGeneratedDocumentText, cancellationToken).ConfigureAwait(false); + var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); solution = solution.WithFrozenSourceGeneratedDocument(identity, sourceText).Project.Solution; } #if DEBUG // make sure created solution has same checksum as given one - await ValidateChecksumAsync(newSolutionChecksum, solution).ConfigureAwait(false); + await ValidateChecksumAsync(newSolutionChecksum, solution, cancellationToken).ConfigureAwait(false); #endif return solution; @@ -104,7 +103,7 @@ public async Task CreateSolutionAsync(Checksum newSolutionChecksum) } } - private async Task UpdateProjectsAsync(Solution solution, ChecksumCollection oldChecksums, ChecksumCollection newChecksums) + private async Task UpdateProjectsAsync(Solution solution, ChecksumCollection oldChecksums, ChecksumCollection newChecksums, CancellationToken cancellationToken) { using var olds = SharedPools.Default>().GetPooledObject(); using var news = SharedPools.Default>().GetPooledObject(); @@ -116,23 +115,23 @@ private async Task UpdateProjectsAsync(Solution solution, ChecksumColl olds.Object.ExceptWith(newChecksums); news.Object.ExceptWith(oldChecksums); - return await UpdateProjectsAsync(solution, olds.Object, news.Object).ConfigureAwait(false); + return await UpdateProjectsAsync(solution, olds.Object, news.Object, cancellationToken).ConfigureAwait(false); } - private async Task UpdateProjectsAsync(Solution solution, HashSet oldChecksums, HashSet newChecksums) + private async Task UpdateProjectsAsync(Solution solution, HashSet oldChecksums, HashSet newChecksums, CancellationToken cancellationToken) { - var oldMap = await GetProjectMapAsync(solution, oldChecksums).ConfigureAwait(false); - var newMap = await GetProjectMapAsync(_assetProvider, newChecksums).ConfigureAwait(false); + var oldMap = await GetProjectMapAsync(solution, oldChecksums, cancellationToken).ConfigureAwait(false); + var newMap = await GetProjectMapAsync(_assetProvider, newChecksums, cancellationToken).ConfigureAwait(false); // bulk sync assets - await SynchronizeAssetsAsync(oldMap, newMap).ConfigureAwait(false); + await SynchronizeAssetsAsync(oldMap, newMap, cancellationToken).ConfigureAwait(false); // added project foreach (var (projectId, newProjectChecksums) in newMap) { if (!oldMap.ContainsKey(projectId)) { - var projectInfo = await _assetProvider.CreateProjectInfoAsync(newProjectChecksums.Checksum, _cancellationToken).ConfigureAwait(false); + var projectInfo = await _assetProvider.CreateProjectInfoAsync(newProjectChecksums.Checksum, cancellationToken).ConfigureAwait(false); if (projectInfo == null) { // this project is not supported in OOP @@ -179,13 +178,13 @@ private async Task UpdateProjectsAsync(Solution solution, HashSet oldMap, Dictionary newMap) + private async Task SynchronizeAssetsAsync(Dictionary oldMap, Dictionary newMap, CancellationToken cancellationToken) { using var pooledObject = SharedPools.Default>().GetPooledObject(); @@ -200,15 +199,15 @@ private async Task SynchronizeAssetsAsync(Dictionary UpdateProjectAsync(Project project, ProjectStateChecksums oldProjectChecksums, ProjectStateChecksums newProjectChecksums) + private async Task UpdateProjectAsync(Project project, ProjectStateChecksums oldProjectChecksums, ProjectStateChecksums newProjectChecksums, CancellationToken cancellationToken) { // changed info if (oldProjectChecksums.Info != newProjectChecksums.Info) { - project = await UpdateProjectInfoAsync(project, newProjectChecksums.Info).ConfigureAwait(false); + project = await UpdateProjectInfoAsync(project, newProjectChecksums.Info, cancellationToken).ConfigureAwait(false); } // changed compilation options @@ -217,34 +216,34 @@ private async Task UpdateProjectAsync(Project project, ProjectStateChe project = project.WithCompilationOptions( project.State.ProjectInfo.Attributes.FixUpCompilationOptions( await _assetProvider.GetAssetAsync( - newProjectChecksums.CompilationOptions, _cancellationToken).ConfigureAwait(false))); + newProjectChecksums.CompilationOptions, cancellationToken).ConfigureAwait(false))); } // changed parse options if (oldProjectChecksums.ParseOptions != newProjectChecksums.ParseOptions) { - project = project.WithParseOptions(await _assetProvider.GetAssetAsync(newProjectChecksums.ParseOptions, _cancellationToken).ConfigureAwait(false)); + project = project.WithParseOptions(await _assetProvider.GetAssetAsync(newProjectChecksums.ParseOptions, cancellationToken).ConfigureAwait(false)); } // changed project references if (oldProjectChecksums.ProjectReferences.Checksum != newProjectChecksums.ProjectReferences.Checksum) { project = project.WithProjectReferences(await _assetProvider.CreateCollectionAsync( - newProjectChecksums.ProjectReferences, _cancellationToken).ConfigureAwait(false)); + newProjectChecksums.ProjectReferences, cancellationToken).ConfigureAwait(false)); } // changed metadata references if (oldProjectChecksums.MetadataReferences.Checksum != newProjectChecksums.MetadataReferences.Checksum) { project = project.WithMetadataReferences(await _assetProvider.CreateCollectionAsync( - newProjectChecksums.MetadataReferences, _cancellationToken).ConfigureAwait(false)); + newProjectChecksums.MetadataReferences, cancellationToken).ConfigureAwait(false)); } // changed analyzer references if (oldProjectChecksums.AnalyzerReferences.Checksum != newProjectChecksums.AnalyzerReferences.Checksum) { project = project.WithAnalyzerReferences(await _assetProvider.CreateCollectionAsync( - newProjectChecksums.AnalyzerReferences, _cancellationToken).ConfigureAwait(false)); + newProjectChecksums.AnalyzerReferences, cancellationToken).ConfigureAwait(false)); } // changed analyzer references @@ -257,7 +256,8 @@ await _assetProvider.GetAssetAsync( oldProjectChecksums.Documents, newProjectChecksums.Documents, (solution, documents) => solution.AddDocuments(documents), - (solution, documentIds) => solution.RemoveDocuments(documentIds)).ConfigureAwait(false); + (solution, documentIds) => solution.RemoveDocuments(documentIds), + cancellationToken).ConfigureAwait(false); } // changed additional documents @@ -270,7 +270,8 @@ await _assetProvider.GetAssetAsync( oldProjectChecksums.AdditionalDocuments, newProjectChecksums.AdditionalDocuments, (solution, documents) => solution.AddAdditionalDocuments(documents), - (solution, documentIds) => solution.RemoveAdditionalDocuments(documentIds)).ConfigureAwait(false); + (solution, documentIds) => solution.RemoveAdditionalDocuments(documentIds), + cancellationToken).ConfigureAwait(false); } // changed analyzer config documents @@ -283,15 +284,16 @@ await _assetProvider.GetAssetAsync( oldProjectChecksums.AnalyzerConfigDocuments, newProjectChecksums.AnalyzerConfigDocuments, (solution, documents) => solution.AddAnalyzerConfigDocuments(documents), - (solution, documentIds) => solution.RemoveAnalyzerConfigDocuments(documentIds)).ConfigureAwait(false); + (solution, documentIds) => solution.RemoveAnalyzerConfigDocuments(documentIds), + cancellationToken).ConfigureAwait(false); } return project.Solution; } - private async Task UpdateProjectInfoAsync(Project project, Checksum infoChecksum) + private async Task UpdateProjectInfoAsync(Project project, Checksum infoChecksum, CancellationToken cancellationToken) { - var newProjectAttributes = await _assetProvider.GetAssetAsync(infoChecksum, _cancellationToken).ConfigureAwait(false); + var newProjectAttributes = await _assetProvider.GetAssetAsync(infoChecksum, cancellationToken).ConfigureAwait(false); // there is no API to change these once project is created Contract.ThrowIfFalse(project.State.ProjectInfo.Attributes.Id == newProjectAttributes.Id); @@ -355,7 +357,8 @@ private async Task UpdateDocumentsAsync( ChecksumCollection oldChecksums, ChecksumCollection newChecksums, Func, Solution> addDocuments, - Func, Solution> removeDocuments) + Func, Solution> removeDocuments, + CancellationToken cancellationToken) { using var olds = SharedPools.Default>().GetPooledObject(); using var news = SharedPools.Default>().GetPooledObject(); @@ -367,15 +370,15 @@ private async Task UpdateDocumentsAsync( olds.Object.ExceptWith(newChecksums); news.Object.ExceptWith(oldChecksums); - var oldMap = await GetDocumentMapAsync(existingTextDocumentStates, olds.Object).ConfigureAwait(false); - var newMap = await GetDocumentMapAsync(_assetProvider, news.Object).ConfigureAwait(false); + var oldMap = await GetDocumentMapAsync(existingTextDocumentStates, olds.Object, cancellationToken).ConfigureAwait(false); + var newMap = await GetDocumentMapAsync(_assetProvider, news.Object, cancellationToken).ConfigureAwait(false); // If more than two documents changed during a single update, perform a bulk synchronization on the // project to avoid large numbers of small synchronization calls during document updates. // 🔗 https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1365014 if (newMap.Count > 2) { - await _assetProvider.SynchronizeProjectAssetsAsync(new[] { projectChecksums.Checksum }, _cancellationToken).ConfigureAwait(false); + await _assetProvider.SynchronizeProjectAssetsAsync(new[] { projectChecksums.Checksum }, cancellationToken).ConfigureAwait(false); } // added document @@ -387,7 +390,7 @@ private async Task UpdateDocumentsAsync( lazyDocumentsToAdd ??= ImmutableArray.CreateBuilder(); // we have new document added - var documentInfo = await _assetProvider.CreateDocumentInfoAsync(newDocumentChecksums.Checksum, _cancellationToken).ConfigureAwait(false); + var documentInfo = await _assetProvider.CreateDocumentInfoAsync(newDocumentChecksums.Checksum, cancellationToken).ConfigureAwait(false); lazyDocumentsToAdd.Add(documentInfo); } } @@ -410,7 +413,7 @@ private async Task UpdateDocumentsAsync( var document = project.GetDocument(documentId) ?? project.GetAdditionalDocument(documentId) ?? project.GetAnalyzerConfigDocument(documentId); Contract.ThrowIfNull(document); - project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums).ConfigureAwait(false); + project = await UpdateDocumentAsync(document, oldDocumentChecksums, newDocumentChecksums, cancellationToken).ConfigureAwait(false); } // removed document @@ -433,19 +436,19 @@ private async Task UpdateDocumentsAsync( return project; } - private async Task UpdateDocumentAsync(TextDocument document, DocumentStateChecksums oldDocumentChecksums, DocumentStateChecksums newDocumentChecksums) + private async Task UpdateDocumentAsync(TextDocument document, DocumentStateChecksums oldDocumentChecksums, DocumentStateChecksums newDocumentChecksums, CancellationToken cancellationToken) { // changed info if (oldDocumentChecksums.Info != newDocumentChecksums.Info) { - document = await UpdateDocumentInfoAsync(document, newDocumentChecksums.Info).ConfigureAwait(false); + document = await UpdateDocumentInfoAsync(document, newDocumentChecksums.Info, cancellationToken).ConfigureAwait(false); } // changed text if (oldDocumentChecksums.Text != newDocumentChecksums.Text) { - var serializableSourceText = await _assetProvider.GetAssetAsync(newDocumentChecksums.Text, _cancellationToken).ConfigureAwait(false); - var sourceText = await serializableSourceText.GetTextAsync(_cancellationToken).ConfigureAwait(false); + var serializableSourceText = await _assetProvider.GetAssetAsync(newDocumentChecksums.Text, cancellationToken).ConfigureAwait(false); + var sourceText = await serializableSourceText.GetTextAsync(cancellationToken).ConfigureAwait(false); document = document.Kind switch { @@ -459,9 +462,9 @@ private async Task UpdateDocumentAsync(TextDocument document, DocumentS return document.Project; } - private async Task UpdateDocumentInfoAsync(TextDocument document, Checksum infoChecksum) + private async Task UpdateDocumentInfoAsync(TextDocument document, Checksum infoChecksum, CancellationToken cancellationToken) { - var newDocumentInfo = await _assetProvider.GetAssetAsync(infoChecksum, _cancellationToken).ConfigureAwait(false); + var newDocumentInfo = await _assetProvider.GetAssetAsync(infoChecksum, cancellationToken).ConfigureAwait(false); // there is no api to change these once document is created Contract.ThrowIfFalse(document.State.Attributes.Id == newDocumentInfo.Id); @@ -487,31 +490,31 @@ private async Task UpdateDocumentInfoAsync(TextDocument document, return document; } - private async Task> GetDocumentMapAsync(AssetProvider assetProvider, HashSet documents) + private static async Task> GetDocumentMapAsync(AssetProvider assetProvider, HashSet documents, CancellationToken cancellationToken) { var map = new Dictionary(); - var documentChecksums = await assetProvider.GetAssetsAsync(documents, _cancellationToken).ConfigureAwait(false); - var infos = await assetProvider.GetAssetsAsync(documentChecksums.Select(p => p.Item2.Info), _cancellationToken).ConfigureAwait(false); + var documentChecksums = await assetProvider.GetAssetsAsync(documents, cancellationToken).ConfigureAwait(false); + var infos = await assetProvider.GetAssetsAsync(documentChecksums.Select(p => p.Item2.Info), cancellationToken).ConfigureAwait(false); foreach (var kv in documentChecksums) { Debug.Assert(assetProvider.EnsureCacheEntryIfExists(kv.Item2.Info), "Expected the prior call to GetAssetsAsync to obtain all items for this loop."); - var info = await assetProvider.GetAssetAsync(kv.Item2.Info, _cancellationToken).ConfigureAwait(false); + var info = await assetProvider.GetAssetAsync(kv.Item2.Info, cancellationToken).ConfigureAwait(false); map.Add(info.Id, kv.Item2); } return map; } - private async Task> GetDocumentMapAsync(IEnumerable states, HashSet documents) + private static async Task> GetDocumentMapAsync(IEnumerable states, HashSet documents, CancellationToken cancellationToken) { var map = new Dictionary(); foreach (var state in states) { - var documentChecksums = await state.GetStateChecksumsAsync(_cancellationToken).ConfigureAwait(false); + var documentChecksums = await state.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); if (documents.Contains(documentChecksums.Checksum)) { map.Add(state.Id, documentChecksums); @@ -521,29 +524,29 @@ private async Task> GetDocumentMa return map; } - private async Task> GetProjectMapAsync(AssetProvider assetProvider, HashSet projects) + private static async Task> GetProjectMapAsync(AssetProvider assetProvider, HashSet projects, CancellationToken cancellationToken) { var map = new Dictionary(); - var projectChecksums = await assetProvider.GetAssetsAsync(projects, _cancellationToken).ConfigureAwait(false); - var infos = await assetProvider.GetAssetsAsync(projectChecksums.Select(p => p.Item2.Info), _cancellationToken).ConfigureAwait(false); + var projectChecksums = await assetProvider.GetAssetsAsync(projects, cancellationToken).ConfigureAwait(false); + var infos = await assetProvider.GetAssetsAsync(projectChecksums.Select(p => p.Item2.Info), cancellationToken).ConfigureAwait(false); foreach (var kv in projectChecksums) { - var info = await assetProvider.GetAssetAsync(kv.Item2.Info, _cancellationToken).ConfigureAwait(false); + var info = await assetProvider.GetAssetAsync(kv.Item2.Info, cancellationToken).ConfigureAwait(false); map.Add(info.Id, kv.Item2); } return map; } - private async Task> GetProjectMapAsync(Solution solution, HashSet projects) + private static async Task> GetProjectMapAsync(Solution solution, HashSet projects, CancellationToken cancellationToken) { var map = new Dictionary(); foreach (var (projectId, projectState) in solution.State.ProjectStates) { - var projectChecksums = await projectState.GetStateChecksumsAsync(_cancellationToken).ConfigureAwait(false); + var projectChecksums = await projectState.GetStateChecksumsAsync(cancellationToken).ConfigureAwait(false); if (projects.Contains(projectChecksums.Checksum)) { map.Add(projectId, projectChecksums); @@ -554,7 +557,7 @@ private async Task> GetProjectMapAs } #if DEBUG - private async Task ValidateChecksumAsync(Checksum checksumFromRequest, Solution incrementalSolutionBuilt) + private async Task ValidateChecksumAsync(Checksum checksumFromRequest, Solution incrementalSolutionBuilt, CancellationToken cancellationToken) { var currentSolutionChecksum = await incrementalSolutionBuilt.State.GetChecksumAsync(CancellationToken.None).ConfigureAwait(false); if (checksumFromRequest == currentSolutionChecksum) @@ -562,7 +565,7 @@ private async Task ValidateChecksumAsync(Checksum checksumFromRequest, Solution return; } - var solutionInfo = await _assetProvider.CreateSolutionInfoAsync(checksumFromRequest, _cancellationToken).ConfigureAwait(false); + var solutionInfo = await _assetProvider.CreateSolutionInfoAsync(checksumFromRequest, cancellationToken).ConfigureAwait(false); var workspace = new AdhocWorkspace(_hostServices); workspace.AddSolution(solutionInfo); diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index d841a6b857deb..3ec33c62aa77d 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -110,7 +110,6 @@ await SlowGetSolutionAndRunAsync( /// the same . /// /// - public ValueTask<(Solution solution, T result)> RunWithSolutionAsync( AssetProvider assetProvider, Checksum solutionChecksum, @@ -285,13 +284,13 @@ private async Task ComputeSolutionAsync( { try { - var updater = new SolutionCreator(Services.HostServices, assetProvider, currentSolution, cancellationToken); + var updater = new SolutionCreator(Services.HostServices, assetProvider, currentSolution); // check whether solution is update to the given base solution - if (await updater.IsIncrementalUpdateAsync(solutionChecksum).ConfigureAwait(false)) + if (await updater.IsIncrementalUpdateAsync(solutionChecksum, cancellationToken).ConfigureAwait(false)) { // create updated solution off the baseSolution - return await updater.CreateSolutionAsync(solutionChecksum).ConfigureAwait(false); + return await updater.CreateSolutionAsync(solutionChecksum, cancellationToken).ConfigureAwait(false); } // we need new solution. bulk sync all asset for the solution first. diff --git a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs index 37cfcc6e7940d..395edd26cdf44 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/SolutionAssetSource.cs @@ -2,15 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Serialization; using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; +using StreamJsonRpc; namespace Microsoft.CodeAnalysis.Remote { @@ -28,12 +32,13 @@ public SolutionAssetSource(ServiceBrokerClient client) // Make sure we are on the thread pool to avoid UI thread dependencies if external code uses ConfigureAwait(true) await TaskScheduler.Default; - using var provider = await _client.GetProxyAsync(SolutionAssetProvider.ServiceDescriptor, cancellationToken).ConfigureAwait(false); - Contract.ThrowIfNull(provider.Proxy); - - return await new RemoteCallback(provider.Proxy).InvokeAsync( - (proxy, pipeWriter, cancellationToken) => proxy.GetAssetsAsync(pipeWriter, solutionChecksum, checksums.ToArray(), cancellationToken), - (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums, serializerService, cancellationToken), + return await RemoteCallback.InvokeServiceAsync( + _client, + SolutionAssetProvider.ServiceDescriptor, + (callback, cancellationToken) => callback.InvokeAsync( + (proxy, pipeWriter, cancellationToken) => proxy.GetAssetsAsync(pipeWriter, solutionChecksum, checksums.ToArray(), cancellationToken), + (pipeReader, cancellationToken) => RemoteHostAssetSerialization.ReadDataAsync(pipeReader, solutionChecksum, checksums, serializerService, cancellationToken), + cancellationToken), cancellationToken).ConfigureAwait(false); } } From bd2bf93d891afb17deae3014f869ac7a126be14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Wed, 15 Jun 2022 17:54:33 -0700 Subject: [PATCH 37/42] Avoid accessing Document from formatter services (#61928) * Avoid accessing Document from formatter services * Push async higher * Split file * Rename --- .../CSharpFormattingInteractionService.cs | 8 +- .../Indentation/CSharpFormatterTestsBase.cs | 12 +- .../SmartTokenFormatterFormatRangeTests.cs | 3 +- .../Formatting/FormatCommandHandler.Paste.cs | 2 +- .../Core/Formatting/FormatCommandHandler.cs | 2 +- .../Extensions/ITextSnapshotExtensions.cs | 6 +- .../Formatting/CoreFormatterTestsBase.cs | 13 +- ...TestFormattingRuleFactoryServiceFactory.cs | 15 +- .../LineCommit/CommitCommandHandler.vb | 2 +- .../VisualBasic/LineCommit/CommitFormatter.vb | 44 +++--- .../SmartTokenFormatter_FormatTokenTests.vb | 2 +- .../VisualBasicFormatterTestBase.vb | 6 +- ...actCurlyBraceOrBracketCompletionService.cs | 57 ++++---- .../BracketBraceCompletionService.cs | 2 +- .../CurlyBraceCompletionService.cs | 6 +- .../Formatting/FormattingRuleUtilities.cs | 28 ++++ .../Shared/Extensions/DocumentExtensions.cs | 16 --- .../Formatting/FormatDocumentOnTypeHandler.cs | 8 +- ...RazorCSharpFormattingInteractionService.cs | 10 +- ...actLanguageService`2.IVsLanguageTextOps.cs | 9 +- ...udioFormattingRuleFactoryServiceFactory.cs | 136 ++++++++---------- .../CSharpSyntaxFormattingService.cs | 74 +++++----- .../Core/Portable/Formatting/Formatter.cs | 18 +-- .../Formatting/ISyntaxFormattingService.cs | 6 +- ...aultFormattingRuleFactoryServiceFactory.cs | 28 ++-- ...stDependentFormattingRuleFactoryService.cs | 8 +- .../Indentation/CSharpSmartTokenFormatter.cs | 11 +- .../AbstractIndentation.Indenter.cs | 9 +- .../Core/Indentation/AbstractIndentation.cs | 4 +- .../Core/Indentation/ISmartTokenFormatter.cs | 2 +- .../VisualBasicSmartTokenFormatter.vb | 10 +- .../CSharpIndentationService.Indenter.cs | 4 +- .../Indentation/AbstractIndentationService.cs | 14 +- .../Core/Utilities/ParsedDocument.cs | 31 ++++ .../Core/Utilities/SemanticDocument.cs | 6 +- .../Core/Utilities/SyntacticDocument.cs | 23 ++- .../Core/WorkspaceExtensions.projitems | 1 + .../VisualBasicIndentationService.Indenter.vb | 1 + .../VisualBasicSyntaxFormattingService.vb | 8 +- 39 files changed, 329 insertions(+), 316 deletions(-) create mode 100644 src/Features/Core/Portable/Formatting/FormattingRuleUtilities.cs create mode 100644 src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/ParsedDocument.cs diff --git a/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs b/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs index 71afe5236003f..0b1d87ccafdbb 100644 --- a/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs +++ b/src/EditorFeatures/CSharp/Formatting/CSharpFormattingInteractionService.cs @@ -107,7 +107,8 @@ public async Task> GetFormattingChangesOnPasteAsync(D var fallbackOptions = _globalOptions.GetCSharpSyntaxFormattingOptions(); var options = await _indentationManager.GetInferredFormattingOptionsAsync(document, fallbackOptions, explicitFormat: true, cancellationToken).ConfigureAwait(false); var service = document.GetRequiredLanguageService(); - return await service.GetFormattingChangesOnPasteAsync(document, textSpan, options, cancellationToken).ConfigureAwait(false); + var documentSyntax = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); + return service.GetFormattingChangesOnPaste(documentSyntax, textSpan, options, cancellationToken); } Task> IFormattingInteractionService.GetFormattingChangesOnReturnAsync( @@ -117,8 +118,9 @@ Task> IFormattingInteractionService.GetFormattingChan public async Task> GetFormattingChangesAsync(Document document, char typedChar, int position, CancellationToken cancellationToken) { var service = document.GetRequiredLanguageService(); + var documentSyntax = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (await service.ShouldFormatOnTypedCharacterAsync(document, typedChar, position, cancellationToken).ConfigureAwait(false)) + if (service.ShouldFormatOnTypedCharacter(documentSyntax, typedChar, position, cancellationToken)) { var fallbackOptions = _globalOptions.GetCSharpSyntaxFormattingOptions(); var formattingOptions = await _indentationManager.GetInferredFormattingOptionsAsync(document, fallbackOptions, explicitFormat: false, cancellationToken).ConfigureAwait(false); @@ -129,7 +131,7 @@ public async Task> GetFormattingChangesAsync(Document IndentStyle = _globalOptions.GetOption(IndentationOptionsStorage.SmartIndent, LanguageNames.CSharp) }; - return await service.GetFormattingChangesOnTypedCharacterAsync(document, position, indentationOptions, cancellationToken).ConfigureAwait(false); + return service.GetFormattingChangesOnTypedCharacter(documentSyntax, position, indentationOptions, cancellationToken); } return ImmutableArray.Empty; diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs index 50ce8977d91ae..17dc9571e37a0 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/CSharpFormatterTestsBase.cs @@ -72,20 +72,20 @@ protected static async Task TokenFormatAsync( private static async Task TokenFormatWorkerAsync(TestWorkspace workspace, ITextBuffer buffer, int indentationLine, char ch, bool useTabs) { var document = buffer.CurrentSnapshot.GetRelatedDocumentsWithChanges().First(); - var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(); + var documentSyntax = await ParsedDocument.CreateAsync(document, CancellationToken.None); - var line = root.GetText().Lines[indentationLine]; + var line = documentSyntax.Text.Lines[indentationLine]; var index = line.ToString().LastIndexOf(ch); Assert.InRange(index, 0, int.MaxValue); // get token var position = line.Start + index; - var token = root.FindToken(position); + var token = documentSyntax.Root.FindToken(position); var formattingRuleProvider = workspace.Services.GetService(); - var rules = ImmutableArray.Create(formattingRuleProvider.CreateRule(document, position)).AddRange(Formatter.GetDefaultFormattingRules(document)); + var rules = ImmutableArray.Create(formattingRuleProvider.CreateRule(documentSyntax, position)).AddRange(Formatter.GetDefaultFormattingRules(document)); var options = new IndentationOptions( new CSharpSyntaxFormattingOptions @@ -96,8 +96,8 @@ private static async Task TokenFormatWorkerAsync(TestWorkspace workspace, ITextB } }); - var formatter = new CSharpSmartTokenFormatter(options, rules, root); - var changes = await formatter.FormatTokenAsync(token, CancellationToken.None); + var formatter = new CSharpSmartTokenFormatter(options, rules, (CompilationUnitSyntax)documentSyntax.Root, documentSyntax.Text); + var changes = formatter.FormatToken(token, CancellationToken.None); ApplyChanges(buffer, changes); } diff --git a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatRangeTests.cs b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatRangeTests.cs index f00b8dab34c87..dd7221eea0b44 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatRangeTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/Indentation/SmartTokenFormatterFormatRangeTests.cs @@ -3576,6 +3576,7 @@ private static async Task AutoFormatOnMarkerAsync(string initialMarkup, string e var position = testDocument.CursorPosition.Value; var document = workspace.CurrentSolution.GetDocument(testDocument.Id); + var documentSyntax = await ParsedDocument.CreateAsync(document, CancellationToken.None); var rules = Formatter.GetDefaultFormattingRules(document); var root = (CompilationUnitSyntax)await document.GetSyntaxRootAsync(); @@ -3590,7 +3591,7 @@ private static async Task AutoFormatOnMarkerAsync(string initialMarkup, string e var options = new IndentationOptions( CSharpSyntaxFormattingOptions.Default.With(new LineFormattingOptions { UseTabs = useTabs })); - var formatter = new CSharpSmartTokenFormatter(options, rules, root); + var formatter = new CSharpSmartTokenFormatter(options, rules, (CompilationUnitSyntax)documentSyntax.Root, documentSyntax.Text); var tokenRange = FormattingRangeHelper.FindAppropriateRange(endToken); if (tokenRange == null) diff --git a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs index e354705be24d0..86aa5ccd90dc6 100644 --- a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs +++ b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.Paste.cs @@ -74,7 +74,7 @@ private void ExecuteCommandWorker(PasteCommandArgs args, SnapshotPoint? caretPos var services = solution.Workspace.Services; var formattingRuleService = services.GetService(); - if (formattingRuleService != null && formattingRuleService.ShouldNotFormatOrCommitOnPaste(document)) + if (formattingRuleService != null && formattingRuleService.ShouldNotFormatOrCommitOnPaste(document.Id)) { return; } diff --git a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs index cfd0f0cb137b3..1dbb6045e9c6b 100644 --- a/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs +++ b/src/EditorFeatures/Core/Formatting/FormatCommandHandler.cs @@ -84,7 +84,7 @@ private static void ApplyChanges(Document document, IList changes, T { var ruleFactory = document.Project.Solution.Workspace.Services.GetRequiredService(); - changes = ruleFactory.FilterFormattedChanges(document, selectionOpt.Value, changes).ToList(); + changes = ruleFactory.FilterFormattedChanges(document.Id, selectionOpt.Value, changes).ToList(); if (changes.Count == 0) { return; diff --git a/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs b/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs index b176a72993e86..27da8122805eb 100644 --- a/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs +++ b/src/EditorFeatures/Core/Shared/Extensions/ITextSnapshotExtensions.cs @@ -33,13 +33,13 @@ public static void FormatAndApplyToBuffer(this ITextSnapshot snapshot, TextSpan return; } - var rules = document.GetFormattingRules(span, additionalRules: null); + var documentSyntax = ParsedDocument.CreateSynchronously(document, cancellationToken); + var rules = FormattingRuleUtilities.GetFormattingRules(documentSyntax, document.Project.LanguageServices, span, additionalRules: null); - var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); var formatter = document.GetRequiredLanguageService(); var options = document.GetSyntaxFormattingOptionsAsync(globalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); - var result = formatter.GetFormattingResult(root, SpecializedCollections.SingletonEnumerable(span), options, rules, cancellationToken); + var result = formatter.GetFormattingResult(documentSyntax.Root, SpecializedCollections.SingletonEnumerable(span), options, rules, cancellationToken); var changes = result.GetTextChanges(cancellationToken); using (Logger.LogBlock(FunctionId.Formatting_ApplyResultToBuffer, cancellationToken)) diff --git a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs index c515bbe30786e..81a541ff6fed8 100644 --- a/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs +++ b/src/EditorFeatures/TestUtilities/Formatting/CoreFormatterTestsBase.cs @@ -182,7 +182,7 @@ private protected async Task AssertFormatAsync(string expected, string code, IEn var buffer = hostdoc.GetTextBuffer(); var document = workspace.CurrentSolution.GetDocument(hostdoc.Id); - var syntaxTree = await document.GetSyntaxTreeAsync(); + var documentSyntax = await ParsedDocument.CreateAsync(document, CancellationToken.None).ConfigureAwait(false); // create new buffer with cloned content var clonedBuffer = EditorFactory.CreateBuffer( @@ -195,23 +195,20 @@ private protected async Task AssertFormatAsync(string expected, string code, IEn { var factory = (TestFormattingRuleFactoryServiceFactory.Factory)formattingRuleProvider; factory.BaseIndentation = baseIndentation.Value; - factory.TextSpan = spans?.First() ?? syntaxTree.GetRoot(CancellationToken.None).FullSpan; + factory.TextSpan = spans?.First() ?? documentSyntax.Root.FullSpan; } - var root = await syntaxTree.GetRootAsync(); - var formattingService = document.GetRequiredLanguageService(); var formattingOptions = (options != null) ? formattingService.GetFormattingOptions(options.ToAnalyzerConfigOptions(document.Project.LanguageServices), fallbackOptions: null) : formattingService.DefaultOptions; - document = workspace.CurrentSolution.GetDocument(syntaxTree); - var rules = formattingRuleProvider.CreateRule(document, 0).Concat(Formatter.GetDefaultFormattingRules(document)); - AssertFormat(workspace, expected, formattingOptions, rules, clonedBuffer, root, spans); + var rules = formattingRuleProvider.CreateRule(documentSyntax, 0).Concat(Formatter.GetDefaultFormattingRules(document)); + AssertFormat(workspace, expected, formattingOptions, rules, clonedBuffer, documentSyntax.Root, spans); // format with node and transform - AssertFormatWithTransformation(workspace, expected, formattingOptions, rules, root, spans); + AssertFormatWithTransformation(workspace, expected, formattingOptions, rules, documentSyntax.Root, spans); } internal void AssertFormatWithTransformation(Workspace workspace, string expected, SyntaxFormattingOptions options, IEnumerable rules, SyntaxNode root, IEnumerable spans) diff --git a/src/EditorFeatures/TestUtilities/Workspaces/TestFormattingRuleFactoryServiceFactory.cs b/src/EditorFeatures/TestUtilities/Workspaces/TestFormattingRuleFactoryServiceFactory.cs index b129058d7619a..eb70704cfb644 100644 --- a/src/EditorFeatures/TestUtilities/Workspaces/TestFormattingRuleFactoryServiceFactory.cs +++ b/src/EditorFeatures/TestUtilities/Workspaces/TestFormattingRuleFactoryServiceFactory.cs @@ -35,25 +35,24 @@ public sealed class Factory : IHostDependentFormattingRuleFactoryService public TextSpan TextSpan = default; public bool UseBaseIndentation = false; - public bool ShouldUseBaseIndentation(Document document) + public bool ShouldUseBaseIndentation(DocumentId documentId) => UseBaseIndentation; - public AbstractFormattingRule CreateRule(Document document, int position) + public bool ShouldNotFormatOrCommitOnPaste(DocumentId documentId) + => UseBaseIndentation; + + public AbstractFormattingRule CreateRule(ParsedDocument document, int position) { if (BaseIndentation == 0) { return NoOpFormattingRule.Instance; } - var root = document.GetSyntaxRootAsync().Result; - return new BaseIndentationFormattingRule(root, TextSpan, BaseIndentation + 4); + return new BaseIndentationFormattingRule(document.Root, TextSpan, BaseIndentation + 4); } - public IEnumerable FilterFormattedChanges(Document document, TextSpan span, IList changes) + public IEnumerable FilterFormattedChanges(DocumentId document, TextSpan span, IList changes) => changes; - - public bool ShouldNotFormatOrCommitOnPaste(Document document) - => UseBaseIndentation; } } } diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitCommandHandler.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitCommandHandler.vb index 75aaba40e4e5c..20acec003dedc 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitCommandHandler.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitCommandHandler.vb @@ -241,7 +241,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Dim document = args.SubjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges() If document IsNot Nothing Then Dim formattingRuleService = document.Project.Solution.Workspace.Services.GetService(Of IHostDependentFormattingRuleFactoryService)() - If formattingRuleService.ShouldNotFormatOrCommitOnPaste(document) Then + If formattingRuleService.ShouldNotFormatOrCommitOnPaste(document.Id) Then transaction.Complete() Return End If diff --git a/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb b/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb index 6a41b14c31b82..d5642a7123556 100644 --- a/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb +++ b/src/EditorFeatures/VisualBasic/LineCommit/CommitFormatter.vb @@ -10,6 +10,7 @@ Imports Microsoft.CodeAnalysis.CodeCleanup Imports Microsoft.CodeAnalysis.CodeCleanup.Providers Imports Microsoft.CodeAnalysis.Formatting Imports Microsoft.CodeAnalysis.Formatting.Rules +Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.CodeAnalysis.Indentation Imports Microsoft.CodeAnalysis.Internal.Log @@ -49,7 +50,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit baseTree As SyntaxTree, cancellationToken As CancellationToken) Implements ICommitFormatter.CommitRegion - Using (Logger.LogBlock(FunctionId.LineCommit_CommitRegion, cancellationToken)) + Using Logger.LogBlock(FunctionId.LineCommit_CommitRegion, cancellationToken) Dim buffer = spanToFormat.Snapshot.TextBuffer Dim currentSnapshot = buffer.CurrentSnapshot @@ -69,8 +70,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Return End If + Dim tree = document.GetSyntaxTreeSynchronously(cancellationToken) + Dim textSpanToFormat = spanToFormat.Span.ToTextSpan() - If AbortForDiagnostics(document, cancellationToken) Then + If AbortForDiagnostics(tree, cancellationToken) Then Return End If @@ -78,7 +81,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Dim fallbackOptions = _globalOptions.GetVisualBasicSyntaxFormattingOptions() Dim formattingOptions = _indentationManager.GetInferredFormattingOptionsAsync(document, fallbackOptions, isExplicitFormat, cancellationToken).WaitAndGetResult(cancellationToken) Dim commitFormattingCleanup = GetCommitFormattingCleanupProvider( - document, + document.Id, + document.Project.LanguageServices, formattingOptions, spanToFormat, baseSnapshot, @@ -129,11 +133,9 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit End Using End Sub - Private Shared Function AbortForDiagnostics(document As Document, cancellationToken As CancellationToken) As Boolean + Private Shared Function AbortForDiagnostics(tree As SyntaxTree, cancellationToken As CancellationToken) As Boolean Const UnterminatedStringId = "BC30648" - Dim tree = document.GetSyntaxTreeSynchronously(cancellationToken) - ' If we have any unterminated strings that overlap what we're trying to format, then ' bail out. It's quite likely the unterminated string will cause a bunch of code to ' swap between real code and string literals, and committing will just cause problems. @@ -144,7 +146,8 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit End Function Private Shared Function GetCommitFormattingCleanupProvider( - document As Document, + documentId As DocumentId, + languageServices As HostLanguageServices, options As SyntaxFormattingOptions, spanToFormat As SnapshotSpan, oldSnapshot As ITextSnapshot, @@ -156,13 +159,14 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Dim oldDirtySpan = newDirtySpan.TranslateTo(oldSnapshot, SpanTrackingMode.EdgeInclusive) ' based on changes made to dirty spans, get right formatting rules to apply - Dim rules = GetFormattingRules(document, options, spanToFormat, oldDirtySpan, oldTree, newDirtySpan, newTree, cancellationToken) + Dim rules = GetFormattingRules(documentId, languageServices, options, spanToFormat, oldDirtySpan, oldTree, newDirtySpan, newTree, cancellationToken) Return New FormatCodeCleanupProvider(rules) End Function Private Shared Function GetFormattingRules( - document As Document, + documentId As DocumentId, + languageServices As HostLanguageServices, options As SyntaxFormattingOptions, spanToFormat As SnapshotSpan, oldDirtySpan As SnapshotSpan, @@ -174,11 +178,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit ' if the span we are going to format is same as the span that got changed, don't bother to do anything special. ' just do full format of the span. If spanToFormat = newDirtySpan Then - Return Formatter.GetDefaultFormattingRules(document) + Return Formatter.GetDefaultFormattingRules(languageServices) End If If oldTree Is Nothing OrElse newTree Is Nothing Then - Return Formatter.GetDefaultFormattingRules(document) + Return Formatter.GetDefaultFormattingRules(languageServices) End If ' TODO: remove this in dev14 @@ -186,10 +190,10 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit ' workaround for VB razor case. ' if we are under VB razor, we always use anchor operation otherwise, due to our double formatting, everything will just get messed. ' this is really a hacky workaround we should remove this in dev14 - Dim formattingRuleService = document.Project.Solution.Workspace.Services.GetService(Of IHostDependentFormattingRuleFactoryService)() + Dim formattingRuleService = languageServices.WorkspaceServices.GetService(Of IHostDependentFormattingRuleFactoryService)() If formattingRuleService IsNot Nothing Then - If formattingRuleService.ShouldUseBaseIndentation(document) Then - Return Formatter.GetDefaultFormattingRules(document) + If formattingRuleService.ShouldUseBaseIndentation(documentId) Then + Return Formatter.GetDefaultFormattingRules(languageServices) End If End If @@ -215,16 +219,16 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit ' for now, do very simple checking. basically, we see whether we get same number of indent operation for the give span. alternative, but little bit ' more expensive and complex, we can actually calculate indentation right after the span, and see whether that is changed. not sure whether that much granularity ' is needed. - If GetNumberOfIndentOperations(document, options, oldTree, oldDirtySpan, cancellationToken) = - GetNumberOfIndentOperations(document, options, newTree, newDirtySpan, cancellationToken) Then - Return (New NoAnchorFormatterRule()).Concat(Formatter.GetDefaultFormattingRules(document)) + If GetNumberOfIndentOperations(languageServices, options, oldTree, oldDirtySpan, cancellationToken) = + GetNumberOfIndentOperations(languageServices, options, newTree, newDirtySpan, cancellationToken) Then + Return (New NoAnchorFormatterRule()).Concat(Formatter.GetDefaultFormattingRules(languageServices)) End If - Return Formatter.GetDefaultFormattingRules(document) + Return Formatter.GetDefaultFormattingRules(languageServices) End Function Private Shared Function GetNumberOfIndentOperations( - document As Document, + languageServices As HostLanguageServices, options As SyntaxFormattingOptions, syntaxTree As SyntaxTree, span As SnapshotSpan, @@ -243,7 +247,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit Dim operations = New List(Of IndentBlockOperation)() While node IsNot Nothing operations.AddRange(FormattingOperations.GetIndentBlockOperations( - Formatter.GetDefaultFormattingRules(document), node, options)) + Formatter.GetDefaultFormattingRules(languageServices), node, options)) node = node.Parent End While diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartTokenFormatter_FormatTokenTests.vb b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartTokenFormatter_FormatTokenTests.vb index 27fe6d9b2c872..32d5f84a8d446 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartTokenFormatter_FormatTokenTests.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/Indentation/SmartTokenFormatter_FormatTokenTests.vb @@ -205,7 +205,7 @@ End Class Dim formatOptions = VisualBasicSyntaxFormattingOptions.Default Dim smartFormatter = New VisualBasicSmartTokenFormatter(formatOptions, formattingRules, root) - Dim changes = Await smartFormatter.FormatTokenAsync(token, Nothing) + Dim changes = smartFormatter.FormatToken(token, Nothing) Using edit = buffer.CreateEdit() For Each change In changes diff --git a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb index c7896480f3e90..e8489c56027d2 100644 --- a/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb +++ b/src/EditorFeatures/VisualBasicTest/Formatting/VisualBasicFormatterTestBase.vb @@ -48,7 +48,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting Dim clonedBuffer = EditorFactory.CreateBuffer(workspace.ExportProvider, buffer.ContentType, buffer.CurrentSnapshot.GetText()) Dim document = workspace.CurrentSolution.GetDocument(hostdoc.Id) - Dim syntaxTree = Await document.GetSyntaxTreeAsync() + Dim docSyntax = Await ParsedDocument.CreateAsync(document, CancellationToken.None) ' Add Base IndentationRule that we had just set up. Dim formattingRuleProvider = workspace.Services.GetService(Of IHostDependentFormattingRuleFactoryService)() @@ -58,11 +58,11 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Formatting factory.TextSpan = span End If - Dim rules = formattingRuleProvider.CreateRule(document, 0).Concat(Formatter.GetDefaultFormattingRules(document)) + Dim rules = formattingRuleProvider.CreateRule(docSyntax, 0).Concat(Formatter.GetDefaultFormattingRules(document)) Dim options = VisualBasicSyntaxFormattingOptions.Default Dim changes = Formatter.GetFormattedTextChanges( - Await syntaxTree.GetRootAsync(), + docSyntax.Root, workspace.Documents.First(Function(d) d.SelectedSpans.Any()).SelectedSpans, workspace.Services, options, diff --git a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs index c4f0c4e48c907..587e246fdbbcf 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/AbstractCurlyBraceOrBracketCompletionService.cs @@ -11,6 +11,7 @@ using Microsoft.CodeAnalysis.BraceCompletion; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.PooledObjects; @@ -30,7 +31,7 @@ internal abstract class AbstractCurlyBraceOrBracketCompletionService : AbstractC protected abstract ImmutableArray GetBraceFormattingIndentationRulesAfterReturn(IndentationOptions options); - protected abstract int AdjustFormattingEndPoint(SourceText text, SyntaxNode root, int startPoint, int endPoint); + protected abstract int AdjustFormattingEndPoint(ParsedDocument document, int startPoint, int endPoint); public sealed override async Task GetTextChangesAfterCompletionAsync(BraceCompletionContext context, IndentationOptions options, CancellationToken cancellationToken) { @@ -45,14 +46,17 @@ internal abstract class AbstractCurlyBraceOrBracketCompletionService : AbstractC return null; } - var (formattingChanges, finalCurlyBraceEnd) = await FormatTrackingSpanAsync( - context.Document, + var documentSyntax = await ParsedDocument.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); + + var (formattingChanges, finalCurlyBraceEnd) = FormatTrackingSpan( + documentSyntax, + context.Document.Project.LanguageServices, context.OpeningPoint, context.ClosingPoint, // We're not trying to format the indented block here, so no need to pass in additional rules. braceFormattingIndentationRules: ImmutableArray.Empty, options, - cancellationToken).ConfigureAwait(false); + cancellationToken); if (formattingChanges.IsEmpty) { @@ -60,8 +64,7 @@ internal abstract class AbstractCurlyBraceOrBracketCompletionService : AbstractC } // The caret location should be at the start of the closing brace character. - var originalText = await context.Document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var formattedText = originalText.WithChanges(formattingChanges); + var formattedText = documentSyntax.Text.WithChanges(formattingChanges); var caretLocation = formattedText.Lines.GetLinePosition(finalCurlyBraceEnd - 1); return new BraceCompletionResult(formattingChanges, caretLocation); @@ -125,14 +128,17 @@ private static bool ContainsOnlyWhitespace(SourceText text, int openingPosition, closingPoint += newLineString.Length; } + var documentSyntax = await ParsedDocument.CreateAsync(document.WithText(textToFormat), cancellationToken).ConfigureAwait(false); + // Format the text that contains the newly inserted line. - var (formattingChanges, newClosingPoint) = await FormatTrackingSpanAsync( - document.WithText(textToFormat), + var (formattingChanges, newClosingPoint) = FormatTrackingSpan( + documentSyntax, + document.Project.LanguageServices, openingPoint, closingPoint, braceFormattingIndentationRules: GetBraceFormattingIndentationRulesAfterReturn(options), options, - cancellationToken).ConfigureAwait(false); + cancellationToken); closingPoint = newClosingPoint; var formattedText = textToFormat.WithChanges(formattingChanges); @@ -202,46 +208,45 @@ static ImmutableArray GetMergedChanges(TextChange newLineEdit, Immut /// Returns the text changes that should be applied to the input document to /// get the formatted text and the end of the close curly brace in the formatted text. /// - private async Task<(ImmutableArray textChanges, int finalBraceEnd)> FormatTrackingSpanAsync( - Document document, + private (ImmutableArray textChanges, int finalBraceEnd) FormatTrackingSpan( + ParsedDocument document, + HostLanguageServices languageServices, int openingPoint, int closingPoint, ImmutableArray braceFormattingIndentationRules, IndentationOptions options, CancellationToken cancellationToken) { - // Annotate the original closing brace so we can find it after formatting. - document = await GetDocumentWithAnnotatedClosingBraceAsync(document, closingPoint, cancellationToken).ConfigureAwait(false); - - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var startPoint = openingPoint; - var endPoint = AdjustFormattingEndPoint(text, root, startPoint, closingPoint); + var endPoint = AdjustFormattingEndPoint(document, startPoint, closingPoint); if (options.IndentStyle == FormattingOptions2.IndentStyle.Smart) { // Set the formatting start point to be the beginning of the first word to the left // of the opening brace location. // skip whitespace - while (startPoint >= 0 && char.IsWhiteSpace(text[startPoint])) + while (startPoint >= 0 && char.IsWhiteSpace(document.Text[startPoint])) { startPoint--; } // skip tokens in the first word to the left. startPoint--; - while (startPoint >= 0 && !char.IsWhiteSpace(text[startPoint])) + while (startPoint >= 0 && !char.IsWhiteSpace(document.Text[startPoint])) { startPoint--; } } var spanToFormat = TextSpan.FromBounds(Math.Max(startPoint, 0), endPoint); - var rules = document.GetFormattingRules(spanToFormat, braceFormattingIndentationRules); - var services = document.Project.Solution.Workspace.Services; + var rules = FormattingRuleUtilities.GetFormattingRules(document, languageServices, spanToFormat, braceFormattingIndentationRules); + + // Annotate the original closing brace so we can find it after formatting. + var annotatedRoot = GetSyntaxRootWithAnnotatedClosingBrace(document.Root, closingPoint); + var result = Formatter.GetFormattingResult( - root, SpecializedCollections.SingletonEnumerable(spanToFormat), services, options.FormattingOptions, rules, cancellationToken); + annotatedRoot, SpecializedCollections.SingletonEnumerable(spanToFormat), languageServices.WorkspaceServices, options.FormattingOptions, rules, cancellationToken); + if (result == null) { return (ImmutableArray.Empty, closingPoint); @@ -253,15 +258,13 @@ static ImmutableArray GetMergedChanges(TextChange newLineEdit, Immut var textChanges = result.GetTextChanges(cancellationToken).ToImmutableArray(); return (textChanges, newClosingPoint); - async Task GetDocumentWithAnnotatedClosingBraceAsync(Document document, int closingBraceEndPoint, CancellationToken cancellationToken) + SyntaxNode GetSyntaxRootWithAnnotatedClosingBrace(SyntaxNode originalRoot, int closingBraceEndPoint) { - var originalRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var closeBraceToken = originalRoot.FindToken(closingBraceEndPoint - 1); Debug.Assert(IsValidClosingBraceToken(closeBraceToken)); var newCloseBraceToken = closeBraceToken.WithAdditionalAnnotations(s_closingBraceSyntaxAnnotation); - var root = originalRoot.ReplaceToken(closeBraceToken, newCloseBraceToken); - return document.WithSyntaxRoot(root); + return originalRoot.ReplaceToken(closeBraceToken, newCloseBraceToken); } } } diff --git a/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs index b18a0e50878fd..92883d1a3531a 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/BracketBraceCompletionService.cs @@ -40,7 +40,7 @@ public override Task AllowOverTypeAsync(BraceCompletionContext context, Ca protected override bool IsValidClosingBraceToken(SyntaxToken token) => token.IsKind(SyntaxKind.CloseBracketToken); - protected override int AdjustFormattingEndPoint(SourceText text, SyntaxNode root, int startPoint, int endPoint) + protected override int AdjustFormattingEndPoint(ParsedDocument document, int startPoint, int endPoint) => endPoint; protected override ImmutableArray GetBraceFormattingIndentationRulesAfterReturn(IndentationOptions options) diff --git a/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs b/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs index 67164dd5f8977..84ef6b5f3ddbc 100644 --- a/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs +++ b/src/Features/CSharp/Portable/BraceCompletion/CurlyBraceCompletionService.cs @@ -58,7 +58,7 @@ protected override bool IsValidOpeningBraceToken(SyntaxToken token) protected override bool IsValidClosingBraceToken(SyntaxToken token) => token.IsKind(SyntaxKind.CloseBraceToken); - protected override int AdjustFormattingEndPoint(SourceText text, SyntaxNode root, int startPoint, int endPoint) + protected override int AdjustFormattingEndPoint(ParsedDocument document, int startPoint, int endPoint) { // Only format outside of the completed braces if they're on the same line for array/collection/object initializer expressions. // Example: `var x = new int[]{}`: @@ -66,9 +66,9 @@ protected override int AdjustFormattingEndPoint(SourceText text, SyntaxNode root // Incorrect: `var x = new int[] { }` // This is a heuristic to prevent brace completion from breaking user expectation/muscle memory in common scenarios. // see bug Devdiv:823958 - if (text.Lines.GetLineFromPosition(startPoint) == text.Lines.GetLineFromPosition(endPoint)) + if (document.Text.Lines.GetLineFromPosition(startPoint) == document.Text.Lines.GetLineFromPosition(endPoint)) { - var startToken = root.FindToken(startPoint, findInsideTrivia: true); + var startToken = document.Root.FindToken(startPoint, findInsideTrivia: true); if (IsValidOpeningBraceToken(startToken) && (startToken.Parent?.IsInitializerForArrayOrCollectionCreationExpression() == true || startToken.Parent is AnonymousObjectCreationExpressionSyntax)) diff --git a/src/Features/Core/Portable/Formatting/FormattingRuleUtilities.cs b/src/Features/Core/Portable/Formatting/FormattingRuleUtilities.cs new file mode 100644 index 0000000000000..3ae4200991149 --- /dev/null +++ b/src/Features/Core/Portable/Formatting/FormattingRuleUtilities.cs @@ -0,0 +1,28 @@ +// 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.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Formatting.Rules; + +internal static class FormattingRuleUtilities +{ + public static ImmutableArray GetFormattingRules(ParsedDocument document, HostLanguageServices languageServices, TextSpan span, IEnumerable? additionalRules) + { + var formattingRuleFactory = languageServices.WorkspaceServices.GetRequiredService(); + // Not sure why this is being done... there aren't any docs on CreateRule either. + var position = (span.Start + span.End) / 2; + + var rules = ImmutableArray.Create(formattingRuleFactory.CreateRule(document, position)); + if (additionalRules != null) + { + rules = rules.AddRange(additionalRules); + } + + return rules.AddRange(Formatter.GetDefaultFormattingRules(languageServices)); + } +} diff --git a/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs b/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs index c979f14a96e80..62af6c97f0b94 100644 --- a/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs +++ b/src/Features/Core/Portable/Shared/Extensions/DocumentExtensions.cs @@ -147,21 +147,5 @@ public static async Task GetApplicableNamingRuleAsync( throw ExceptionUtilities.Unreachable; } - - public static ImmutableArray GetFormattingRules(this Document document, TextSpan span, IEnumerable? additionalRules) - { - var workspace = document.Project.Solution.Workspace; - var formattingRuleFactory = workspace.Services.GetRequiredService(); - // Not sure why this is being done... there aren't any docs on CreateRule either. - var position = (span.Start + span.End) / 2; - - var rules = ImmutableArray.Create(formattingRuleFactory.CreateRule(document, position)); - if (additionalRules != null) - { - rules = rules.AddRange(additionalRules); - } - - return rules.AddRange(Formatter.GetDefaultFormattingRules(document)); - } } } diff --git a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs index 22c4919e014db..69203f3d12d96 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Formatting/FormatDocumentOnTypeHandler.cs @@ -56,8 +56,9 @@ public FormatDocumentOnTypeHandler(IGlobalOptionService globalOptions) } var formattingService = document.Project.LanguageServices.GetRequiredService(); + var documentSyntax = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (!await formattingService.ShouldFormatOnTypedCharacterAsync(document, request.Character[0], position, cancellationToken).ConfigureAwait(false)) + if (!formattingService.ShouldFormatOnTypedCharacter(documentSyntax, request.Character[0], position, cancellationToken)) { return Array.Empty(); } @@ -69,15 +70,14 @@ public FormatDocumentOnTypeHandler(IGlobalOptionService globalOptions) AutoFormattingOptions = _globalOptions.GetAutoFormattingOptions(document.Project.Language) }; - var textChanges = await formattingService.GetFormattingChangesOnTypedCharacterAsync(document, position, indentationOptions, cancellationToken).ConfigureAwait(false); + var textChanges = formattingService.GetFormattingChangesOnTypedCharacter(documentSyntax, position, indentationOptions, cancellationToken); if (textChanges.IsEmpty) { return Array.Empty(); } var edits = new ArrayBuilder(); - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, text))); + edits.AddRange(textChanges.Select(change => ProtocolConversions.TextChangeToTextEdit(change, documentSyntax.Text))); return edits.ToArrayAndFree(); } } diff --git a/src/Tools/ExternalAccess/Razor/RazorCSharpFormattingInteractionService.cs b/src/Tools/ExternalAccess/Razor/RazorCSharpFormattingInteractionService.cs index 2f704a46ec10c..5723d227aa6cc 100644 --- a/src/Tools/ExternalAccess/Razor/RazorCSharpFormattingInteractionService.cs +++ b/src/Tools/ExternalAccess/Razor/RazorCSharpFormattingInteractionService.cs @@ -38,8 +38,9 @@ public static async Task> GetFormattingChangesAsync( { Contract.ThrowIfFalse(document.Project.Language is LanguageNames.CSharp); var formattingService = document.GetRequiredLanguageService(); + var documentSyntax = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (!await formattingService.ShouldFormatOnTypedCharacterAsync(document, typedChar, position, cancellationToken).ConfigureAwait(false)) + if (!formattingService.ShouldFormatOnTypedCharacter(documentSyntax, typedChar, position, cancellationToken)) { return ImmutableArray.Empty; } @@ -58,7 +59,7 @@ public static async Task> GetFormattingChangesAsync( AutoFormattingOptions = globalOptions.GetAutoFormattingOptions(languageServices) }; - return await formattingService.GetFormattingChangesOnTypedCharacterAsync(document, position, indentationOptions, cancellationToken).ConfigureAwait(false); + return formattingService.GetFormattingChangesOnTypedCharacter(documentSyntax, position, indentationOptions, cancellationToken); } /// @@ -77,8 +78,9 @@ public static async Task> GetFormattingChangesAsync( { Contract.ThrowIfFalse(document.Project.Language is LanguageNames.CSharp); var formattingService = document.GetRequiredLanguageService(); + var documentSyntax = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - if (!await formattingService.ShouldFormatOnTypedCharacterAsync(document, typedChar, position, cancellationToken).ConfigureAwait(false)) + if (!formattingService.ShouldFormatOnTypedCharacter(documentSyntax, typedChar, position, cancellationToken)) { return ImmutableArray.Empty; } @@ -90,7 +92,7 @@ public static async Task> GetFormattingChangesAsync( IndentStyle = (FormattingOptions2.IndentStyle)indentStyle }; - return await formattingService.GetFormattingChangesOnTypedCharacterAsync(document, position, roslynIndentationOptions, cancellationToken).ConfigureAwait(false); + return formattingService.GetFormattingChangesOnTypedCharacter(documentSyntax, position, roslynIndentationOptions, cancellationToken); } public static IList GetFormattedTextChanges( diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs index 2d9dd95d7751a..f8f00dc6f2617 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageTextOps.cs @@ -53,8 +53,9 @@ private int FormatWorker(IVsTextLayer textLayer, TextSpan[] selections, Cancella return VSConstants.E_FAIL; } - var root = document.GetSyntaxRootSynchronously(cancellationToken); - var text = root.SyntaxTree.GetText(cancellationToken); + var documentSyntax = ParsedDocument.CreateSynchronously(document, cancellationToken); + var text = documentSyntax.Text; + var root = documentSyntax.Root; var formattingOptions = document.GetSyntaxFormattingOptionsAsync(GlobalOptions, cancellationToken).AsTask().WaitAndGetResult(cancellationToken); var ts = selections.Single(); @@ -65,7 +66,7 @@ private int FormatWorker(IVsTextLayer textLayer, TextSpan[] selections, Cancella // Since we know we are on the UI thread, lets get the base indentation now, so that there is less // cleanup work to do later in Venus. var ruleFactory = Workspace.Services.GetService(); - var rules = ruleFactory.CreateRule(document, start).Concat(Formatter.GetDefaultFormattingRules(document)); + var rules = ruleFactory.CreateRule(documentSyntax, start).Concat(Formatter.GetDefaultFormattingRules(document.Project.LanguageServices)); // use formatting that return text changes rather than tree rewrite which is more expensive var formatter = document.GetRequiredLanguageService(); @@ -73,7 +74,7 @@ private int FormatWorker(IVsTextLayer textLayer, TextSpan[] selections, Cancella .GetTextChanges(cancellationToken); var originalSpan = RoslynTextSpan.FromBounds(start, end); - var formattedChanges = ruleFactory.FilterFormattedChanges(document, originalSpan, originalChanges); + var formattedChanges = ruleFactory.FilterFormattedChanges(document.Id, originalSpan, originalChanges); if (formattedChanges.IsEmpty()) { return VSConstants.S_OK; diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioFormattingRuleFactoryServiceFactory.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioFormattingRuleFactoryServiceFactory.cs index 4cf96042f4750..6aeba2ae7b3c3 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioFormattingRuleFactoryServiceFactory.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioFormattingRuleFactoryServiceFactory.cs @@ -22,119 +22,99 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation { - [ExportWorkspaceServiceFactory(typeof(IHostDependentFormattingRuleFactoryService), ServiceLayer.Host), Shared] - internal sealed class VisualStudioFormattingRuleFactoryServiceFactory : IWorkspaceServiceFactory + [ExportWorkspaceService(typeof(IHostDependentFormattingRuleFactoryService), ServiceLayer.Host), Shared] + internal sealed class VisualStudioFormattingRuleFactoryService : IHostDependentFormattingRuleFactoryService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public VisualStudioFormattingRuleFactoryServiceFactory() + public VisualStudioFormattingRuleFactoryService() { } + public bool ShouldUseBaseIndentation(DocumentId documentId) + => IsContainedDocument(documentId); - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new Factory(); + public bool ShouldNotFormatOrCommitOnPaste(DocumentId documentId) + => IsContainedDocument(documentId); - private sealed class Factory : IHostDependentFormattingRuleFactoryService - { - public bool ShouldUseBaseIndentation(Document document) - => IsContainedDocument(document); - - public bool ShouldNotFormatOrCommitOnPaste(Document document) - => IsContainedDocument(document); + private static bool IsContainedDocument(DocumentId documentId) + => ContainedDocument.TryGetContainedDocument(documentId) != null; - private static bool IsContainedDocument(Document document) + public AbstractFormattingRule CreateRule(ParsedDocument document, int position) + { + var containedDocument = ContainedDocument.TryGetContainedDocument(document.Id); + if (containedDocument == null) { - var visualStudioWorkspace = document.Project.Solution.Workspace as VisualStudioWorkspaceImpl; - return visualStudioWorkspace?.TryGetContainedDocument(document.Id) != null; + return NoOpFormattingRule.Instance; } - public AbstractFormattingRule CreateRule(Document document, int position) + var textContainer = document.Text.Container; + if (textContainer.TryGetTextBuffer() is not IProjectionBuffer) { - if (document.Project.Solution.Workspace is not VisualStudioWorkspaceImpl visualStudioWorkspace) - { - return NoOpFormattingRule.Instance; - } + return NoOpFormattingRule.Instance; + } - var containedDocument = visualStudioWorkspace.TryGetContainedDocument(document.Id); - if (containedDocument == null) - { - return NoOpFormattingRule.Instance; - } + using var pooledObject = SharedPools.Default>().GetPooledObject(); + var spans = pooledObject.Object; - var textContainer = document.GetTextSynchronously(CancellationToken.None).Container; - if (textContainer.TryGetTextBuffer() is not IProjectionBuffer) - { - return NoOpFormattingRule.Instance; - } + var root = document.Root; + var text = document.Text; - using var pooledObject = SharedPools.Default>().GetPooledObject(); - var spans = pooledObject.Object; + spans.AddRange(containedDocument.GetEditorVisibleSpans()); - var root = document.GetSyntaxRootSynchronously(CancellationToken.None); - var text = root.SyntaxTree.GetText(CancellationToken.None); + for (var i = 0; i < spans.Count; i++) + { + var visibleSpan = spans[i]; + if (visibleSpan.IntersectsWith(position) || visibleSpan.End == position) + { + return containedDocument.GetBaseIndentationRule(root, text, spans, i); + } + } - spans.AddRange(containedDocument.GetEditorVisibleSpans()); + // in razor (especially in @helper tag), it is possible for us to be asked for next line of visible span + var line = text.Lines.GetLineFromPosition(position); + if (line.LineNumber > 0) + { + line = text.Lines[line.LineNumber - 1]; + // find one that intersects with previous line for (var i = 0; i < spans.Count; i++) { var visibleSpan = spans[i]; - if (visibleSpan.IntersectsWith(position) || visibleSpan.End == position) + if (visibleSpan.IntersectsWith(line.Span)) { return containedDocument.GetBaseIndentationRule(root, text, spans, i); } } + } - // in razor (especially in @helper tag), it is possible for us to be asked for next line of visible span - var line = text.Lines.GetLineFromPosition(position); - if (line.LineNumber > 0) - { - line = text.Lines[line.LineNumber - 1]; + FatalError.ReportAndCatch( + new InvalidOperationException($"Can't find an intersection. Visible spans count: {spans.Count}")); - // find one that intersects with previous line - for (var i = 0; i < spans.Count; i++) - { - var visibleSpan = spans[i]; - if (visibleSpan.IntersectsWith(line.Span)) - { - return containedDocument.GetBaseIndentationRule(root, text, spans, i); - } - } - } - - FatalError.ReportAndCatch( - new InvalidOperationException($"Can't find an intersection. Visible spans count: {spans.Count}")); + return NoOpFormattingRule.Instance; + } - return NoOpFormattingRule.Instance; + public IEnumerable FilterFormattedChanges(DocumentId documentId, TextSpan span, IList changes) + { + var containedDocument = ContainedDocument.TryGetContainedDocument(documentId); + if (containedDocument == null) + { + return changes; } - public IEnumerable FilterFormattedChanges(Document document, TextSpan span, IList changes) + // in case of a Venus, when format document command is issued, Venus will call format API with each script block spans. + // in that case, we need to make sure formatter doesn't overstep other script blocks content. in actual format selection case, + // we need to format more than given selection otherwise, we will not adjust indentation of first token of the given selection. + foreach (var visibleSpan in containedDocument.GetEditorVisibleSpans()) { - if (document.Project.Solution.Workspace is not VisualStudioWorkspaceImpl visualStudioWorkspace) + if (visibleSpan != span) { - return changes; + continue; } - var containedDocument = visualStudioWorkspace.TryGetContainedDocument(document.Id); - if (containedDocument == null) - { - return changes; - } - - // in case of a Venus, when format document command is issued, Venus will call format API with each script block spans. - // in that case, we need to make sure formatter doesn't overstep other script blocks content. in actual format selection case, - // we need to format more than given selection otherwise, we will not adjust indentation of first token of the given selection. - foreach (var visibleSpan in containedDocument.GetEditorVisibleSpans()) - { - if (visibleSpan != span) - { - continue; - } - - return changes.Where(c => span.IntersectsWith(c.Span)); - } - - return changes; + return changes.Where(c => span.IntersectsWith(c.Span)); } + + return changes; } } } diff --git a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs index cb8952e0d8cd3..8232e6dadf7bc 100644 --- a/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs +++ b/src/Workspaces/CSharp/Portable/Formatting/CSharpSyntaxFormattingService.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.CSharp.Utilities; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Formatting.Rules; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Indentation; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -24,29 +25,38 @@ namespace Microsoft.CodeAnalysis.CSharp.Formatting { - [ExportLanguageService(typeof(ISyntaxFormattingService), LanguageNames.CSharp), Shared] internal sealed class CSharpSyntaxFormattingService : CSharpSyntaxFormatting, ISyntaxFormattingService { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpSyntaxFormattingService() + private readonly HostLanguageServices _services; + + [ExportLanguageServiceFactory(typeof(ISyntaxFormattingService), LanguageNames.CSharp), Shared] + internal sealed class Factory : ILanguageServiceFactory { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Factory() + { + } + + public ILanguageService CreateLanguageService(HostLanguageServices languageServices) + => new CSharpSyntaxFormattingService(languageServices); } - public async Task ShouldFormatOnTypedCharacterAsync( - Document document, + private CSharpSyntaxFormattingService(HostLanguageServices languageServices) + => _services = languageServices; + + public bool ShouldFormatOnTypedCharacter( + ParsedDocument documentSyntax, char typedChar, int caretPosition, CancellationToken cancellationToken) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - // first, find the token user just typed. - var token = root.FindToken(Math.Max(0, caretPosition - 1), findInsideTrivia: true); + var token = documentSyntax.Root.FindToken(Math.Max(0, caretPosition - 1), findInsideTrivia: true); if (token.IsMissing || !ValidSingleOrMultiCharactersTokenKind(typedChar, token.Kind()) || token.IsKind(SyntaxKind.EndOfFileToken, SyntaxKind.None) || - root.SyntaxTree.IsInNonUserCode(caretPosition, cancellationToken)) + documentSyntax.SyntaxTree.IsInNonUserCode(caretPosition, cancellationToken)) { return false; } @@ -71,8 +81,7 @@ public async Task ShouldFormatOnTypedCharacterAsync( // mess with it if it's inside a line. if (token.IsKind(SyntaxKind.OpenBraceToken)) { - var text = await root.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); - if (!token.IsFirstTokenOnLine(text)) + if (!token.IsFirstTokenOnLine(documentSyntax.Text)) { return false; } @@ -81,13 +90,13 @@ public async Task ShouldFormatOnTypedCharacterAsync( return true; } - public async Task> GetFormattingChangesOnTypedCharacterAsync( - Document document, + public ImmutableArray GetFormattingChangesOnTypedCharacter( + ParsedDocument document, int caretPosition, IndentationOptions indentationOptions, CancellationToken cancellationToken) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var root = document.Root; var token = root.FindToken(Math.Max(0, caretPosition - 1), findInsideTrivia: true); var formattingRules = GetFormattingRules(document, caretPosition, token); @@ -128,18 +137,18 @@ public async Task> GetFormattingChangesOnTypedCharact // if we're only doing smart indent, then ignore all edits to this token that occur before // the span of the token. They're irrelevant and may screw up other code the user doesn't // want touched. - var tokenEdits = await FormatTokenAsync(root, indentationOptions, token, formattingRules, cancellationToken).ConfigureAwait(false); + var tokenEdits = FormatToken(document, indentationOptions, token, formattingRules, cancellationToken); return tokenEdits.Where(t => t.Span.Start >= token.FullSpan.Start).ToImmutableArray(); } // if formatting range fails, do format token one at least - var changes = FormatRange(root, indentationOptions, token, formattingRules, cancellationToken); + var changes = FormatRange(document, indentationOptions, token, formattingRules, cancellationToken); if (changes.Length > 0) { return changes; } - return (await FormatTokenAsync(root, indentationOptions, token, formattingRules, cancellationToken).ConfigureAwait(false)).ToImmutableArray(); + return FormatToken(document, indentationOptions, token, formattingRules, cancellationToken).ToImmutableArray(); } private static bool OnlySmartIndentCloseBrace(in AutoFormattingOptions options) @@ -157,15 +166,15 @@ private static bool OnlySmartIndentOpenBrace(in AutoFormattingOptions options) return !options.FormatOnTyping; } - private static async Task> FormatTokenAsync( - SyntaxNode root, IndentationOptions options, SyntaxToken token, ImmutableArray formattingRules, CancellationToken cancellationToken) + private static IList FormatToken( + ParsedDocument document, IndentationOptions options, SyntaxToken token, ImmutableArray formattingRules, CancellationToken cancellationToken) { - var formatter = new CSharpSmartTokenFormatter(options, formattingRules, (CompilationUnitSyntax)root); - return await formatter.FormatTokenAsync(token, cancellationToken).ConfigureAwait(false); + var formatter = new CSharpSmartTokenFormatter(options, formattingRules, (CompilationUnitSyntax)document.Root, document.Text); + return formatter.FormatToken(token, cancellationToken); } private static ImmutableArray FormatRange( - SyntaxNode root, + ParsedDocument document, IndentationOptions options, SyntaxToken endToken, ImmutableArray formattingRules, @@ -187,7 +196,7 @@ private static ImmutableArray FormatRange( return ImmutableArray.Empty; } - var formatter = new CSharpSmartTokenFormatter(options, formattingRules, (CompilationUnitSyntax)root); + var formatter = new CSharpSmartTokenFormatter(options, formattingRules, (CompilationUnitSyntax)document.Root, document.Text); var changes = formatter.FormatRange(tokenRange.Value.Item1, tokenRange.Value.Item2, cancellationToken); return changes.ToImmutableArray(); @@ -305,26 +314,23 @@ private static bool IsInvalidTokenKind(SyntaxToken token) token.IsKind(SyntaxKind.EndOfFileToken); } - private static ImmutableArray GetFormattingRules(Document document, int position, SyntaxToken tokenBeforeCaret) + private ImmutableArray GetFormattingRules(ParsedDocument document, int position, SyntaxToken tokenBeforeCaret) { - var workspace = document.Project.Solution.Workspace; - var formattingRuleFactory = workspace.Services.GetRequiredService(); + var formattingRuleFactory = _services.WorkspaceServices.GetRequiredService(); return ImmutableArray.Create(formattingRuleFactory.CreateRule(document, position)) .AddRange(GetTypingRules(tokenBeforeCaret)) - .AddRange(Formatter.GetDefaultFormattingRules(document)); + .AddRange(Formatter.GetDefaultFormattingRules(_services)); } - public async Task> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken) + public ImmutableArray GetFormattingChangesOnPaste(ParsedDocument document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - - var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(root, textSpan); - var service = document.GetRequiredLanguageService(); + var formattingSpan = CommonFormattingHelpers.GetFormattingSpan(document.Root, textSpan); + var service = _services.GetRequiredService(); var rules = new List() { new PasteFormattingRule() }; rules.AddRange(service.GetDefaultFormattingRules()); - var result = service.GetFormattingResult(root, SpecializedCollections.SingletonEnumerable(formattingSpan), options, rules, cancellationToken); + var result = service.GetFormattingResult(document.Root, SpecializedCollections.SingletonEnumerable(formattingSpan), options, rules, cancellationToken); return result.GetTextChanges(cancellationToken).ToImmutableArray(); } diff --git a/src/Workspaces/Core/Portable/Formatting/Formatter.cs b/src/Workspaces/Core/Portable/Formatting/Formatter.cs index 79105b2e76570..0d7582888c9b5 100644 --- a/src/Workspaces/Core/Portable/Formatting/Formatter.cs +++ b/src/Workspaces/Core/Portable/Formatting/Formatter.cs @@ -34,22 +34,10 @@ public static class Formatter /// Gets the formatting rules that would be applied if left unspecified. /// internal static ImmutableArray GetDefaultFormattingRules(Document document) - { - if (document == null) - { - throw new ArgumentNullException(nameof(document)); - } + => GetDefaultFormattingRules(document.Project.LanguageServices); - var service = document.GetLanguageService(); - if (service != null) - { - return service.GetDefaultFormattingRules(); - } - else - { - return ImmutableArray.Empty; - } - } + internal static ImmutableArray GetDefaultFormattingRules(HostLanguageServices languageServices) + => languageServices.GetService()?.GetDefaultFormattingRules() ?? ImmutableArray.Empty; /// /// Formats the whitespace in a document. diff --git a/src/Workspaces/Core/Portable/Formatting/ISyntaxFormattingService.cs b/src/Workspaces/Core/Portable/Formatting/ISyntaxFormattingService.cs index 7ba21b257f44e..6105641b6c374 100644 --- a/src/Workspaces/Core/Portable/Formatting/ISyntaxFormattingService.cs +++ b/src/Workspaces/Core/Portable/Formatting/ISyntaxFormattingService.cs @@ -14,8 +14,8 @@ namespace Microsoft.CodeAnalysis.Formatting { internal interface ISyntaxFormattingService : ISyntaxFormatting, ILanguageService { - Task ShouldFormatOnTypedCharacterAsync(Document document, char typedChar, int caretPosition, CancellationToken cancellationToken); - Task> GetFormattingChangesOnTypedCharacterAsync(Document document, int caretPosition, IndentationOptions indentationOptions, CancellationToken cancellationToken); - Task> GetFormattingChangesOnPasteAsync(Document document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken); + bool ShouldFormatOnTypedCharacter(ParsedDocument document, char typedChar, int caretPosition, CancellationToken cancellationToken); + ImmutableArray GetFormattingChangesOnTypedCharacter(ParsedDocument document, int caretPosition, IndentationOptions indentationOptions, CancellationToken cancellationToken); + ImmutableArray GetFormattingChangesOnPaste(ParsedDocument document, TextSpan textSpan, SyntaxFormattingOptions options, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/Core/Portable/Formatting/Rules/DefaultFormattingRuleFactoryServiceFactory.cs b/src/Workspaces/Core/Portable/Formatting/Rules/DefaultFormattingRuleFactoryServiceFactory.cs index 918117c2289d0..eca3850d8a30b 100644 --- a/src/Workspaces/Core/Portable/Formatting/Rules/DefaultFormattingRuleFactoryServiceFactory.cs +++ b/src/Workspaces/Core/Portable/Formatting/Rules/DefaultFormattingRuleFactoryServiceFactory.cs @@ -11,31 +11,25 @@ namespace Microsoft.CodeAnalysis.Formatting.Rules { - [ExportWorkspaceServiceFactory(typeof(IHostDependentFormattingRuleFactoryService), ServiceLayer.Default), Shared] - internal sealed class DefaultFormattingRuleFactoryServiceFactory : IWorkspaceServiceFactory + [ExportWorkspaceService(typeof(IHostDependentFormattingRuleFactoryService), ServiceLayer.Default), Shared] + internal sealed class DefaultFormattingRuleFactoryService : IHostDependentFormattingRuleFactoryService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public DefaultFormattingRuleFactoryServiceFactory() + public DefaultFormattingRuleFactoryService() { } - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new Factory(); + public bool ShouldNotFormatOrCommitOnPaste(DocumentId documentId) + => false; - private sealed class Factory : IHostDependentFormattingRuleFactoryService - { - public bool ShouldUseBaseIndentation(Document document) - => false; - - public AbstractFormattingRule CreateRule(Document document, int position) - => NoOpFormattingRule.Instance; + public bool ShouldUseBaseIndentation(DocumentId documentId) + => false; - public IEnumerable FilterFormattedChanges(Document document, TextSpan span, IList changes) - => changes; + public AbstractFormattingRule CreateRule(ParsedDocument document, int position) + => NoOpFormattingRule.Instance; - public bool ShouldNotFormatOrCommitOnPaste(Document document) - => false; - } + public IEnumerable FilterFormattedChanges(DocumentId document, TextSpan span, IList changes) + => changes; } } diff --git a/src/Workspaces/Core/Portable/Formatting/Rules/IHostDependentFormattingRuleFactoryService.cs b/src/Workspaces/Core/Portable/Formatting/Rules/IHostDependentFormattingRuleFactoryService.cs index 291bc02fb7107..fc78133f336a6 100644 --- a/src/Workspaces/Core/Portable/Formatting/Rules/IHostDependentFormattingRuleFactoryService.cs +++ b/src/Workspaces/Core/Portable/Formatting/Rules/IHostDependentFormattingRuleFactoryService.cs @@ -10,9 +10,9 @@ namespace Microsoft.CodeAnalysis.Formatting.Rules { internal interface IHostDependentFormattingRuleFactoryService : IWorkspaceService { - bool ShouldNotFormatOrCommitOnPaste(Document document); - bool ShouldUseBaseIndentation(Document document); - AbstractFormattingRule CreateRule(Document document, int position); - IEnumerable FilterFormattedChanges(Document document, TextSpan span, IList changes); + bool ShouldNotFormatOrCommitOnPaste(DocumentId documentId); + bool ShouldUseBaseIndentation(DocumentId documentId); + AbstractFormattingRule CreateRule(ParsedDocument document, int position); + IEnumerable FilterFormattedChanges(DocumentId documentId, TextSpan span, IList changes); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs index e3ea707a9dede..0028cccdcf57d 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Indentation/CSharpSmartTokenFormatter.cs @@ -25,11 +25,13 @@ internal class CSharpSmartTokenFormatter : ISmartTokenFormatter private readonly ImmutableArray _formattingRules; private readonly CompilationUnitSyntax _root; + private readonly SourceText _text; public CSharpSmartTokenFormatter( IndentationOptions options, ImmutableArray formattingRules, - CompilationUnitSyntax root) + CompilationUnitSyntax root, + SourceText text) { Contract.ThrowIfNull(root); @@ -37,6 +39,7 @@ public CSharpSmartTokenFormatter( _formattingRules = formattingRules; _root = root; + _text = text; } public IList FormatRange( @@ -72,8 +75,7 @@ private static bool CloseBraceOfTryOrDoBlock(SyntaxToken endToken) (endToken.Parent.IsParentKind(SyntaxKind.TryStatement) || endToken.Parent.IsParentKind(SyntaxKind.DoStatement)); } - public async Task> FormatTokenAsync( - SyntaxToken token, CancellationToken cancellationToken) + public IList FormatToken(SyntaxToken token, CancellationToken cancellationToken) { Contract.ThrowIfTrue(token.Kind() is SyntaxKind.None or SyntaxKind.EndOfFileToken); @@ -108,8 +110,7 @@ public async Task> FormatTokenAsync( _options.IndentStyle != FormattingOptions2.IndentStyle.Smart) { RoslynDebug.AssertNotNull(token.SyntaxTree); - var text = await token.SyntaxTree.GetTextAsync(cancellationToken).ConfigureAwait(false); - if (token.IsFirstTokenOnLine(text)) + if (token.IsFirstTokenOnLine(_text)) { adjustedStartPosition = token.SpanStart; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/AbstractIndentation.Indenter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/AbstractIndentation.Indenter.cs index d6d5b54765976..638a3cd88deaa 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/AbstractIndentation.Indenter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/AbstractIndentation.Indenter.cs @@ -167,14 +167,9 @@ public bool TryGetSmartTokenIndentation(out IndentationResult indentationResult) { if (_service.ShouldUseTokenIndenter(this, out var token)) { - // var root = document.GetSyntaxRootSynchronously(cancellationToken); - var sourceText = Tree.GetText(CancellationToken); + var changes = SmartTokenFormatter.FormatToken(token, CancellationToken); - var changes = this.SmartTokenFormatter - .FormatTokenAsync(token, CancellationToken) - .WaitAndGetResult_CanCallOnBackground(CancellationToken); - - var updatedSourceText = sourceText.WithChanges(changes); + var updatedSourceText = Text.WithChanges(changes); if (LineToBeIndented.LineNumber < updatedSourceText.Lines.Count) { var updatedLine = updatedSourceText.Lines[LineToBeIndented.LineNumber]; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/AbstractIndentation.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/AbstractIndentation.cs index 6e22615ad945a..48dd1fdb7976c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/AbstractIndentation.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/AbstractIndentation.cs @@ -21,12 +21,12 @@ internal abstract partial class AbstractIndentation /// /// Returns if the language specific should be deferred to figure out indentation. If so, it - /// will be asked to the resultant + /// will be asked to the resultant /// provided by this method. /// protected abstract bool ShouldUseTokenIndenter(Indenter indenter, out SyntaxToken token); protected abstract ISmartTokenFormatter CreateSmartTokenFormatter( - TSyntaxRoot root, TextLine lineToBeIndented, IndentationOptions options, AbstractFormattingRule baseFormattingRule); + TSyntaxRoot root, SourceText text, TextLine lineToBeIndented, IndentationOptions options, AbstractFormattingRule baseFormattingRule); protected abstract IndentationResult? GetDesiredIndentationWorker( Indenter indenter, SyntaxToken? token, SyntaxTrivia? trivia); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/ISmartTokenFormatter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/ISmartTokenFormatter.cs index 7b5e1dc398d5c..ff0a8a6010482 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/ISmartTokenFormatter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Indentation/ISmartTokenFormatter.cs @@ -11,6 +11,6 @@ namespace Microsoft.CodeAnalysis.Indentation { internal interface ISmartTokenFormatter { - Task> FormatTokenAsync(SyntaxToken token, CancellationToken cancellationToken); + IList FormatToken(SyntaxToken token, CancellationToken cancellationToken); } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Indentation/VisualBasicSmartTokenFormatter.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Indentation/VisualBasicSmartTokenFormatter.vb index 79ab32b27a872..07d4675771c7f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Indentation/VisualBasicSmartTokenFormatter.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Indentation/VisualBasicSmartTokenFormatter.vb @@ -27,13 +27,13 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Indentation root As CompilationUnitSyntax) Contract.ThrowIfNull(root) - Me._options = options - Me._formattingRules = formattingRules + _options = options + _formattingRules = formattingRules - Me._root = root + _root = root End Sub - Public Function FormatTokenAsync(token As SyntaxToken, cancellationToken As CancellationToken) As Tasks.Task(Of IList(Of TextChange)) Implements ISmartTokenFormatter.FormatTokenAsync + Public Function FormatToken(token As SyntaxToken, cancellationToken As CancellationToken) As IList(Of TextChange) Implements ISmartTokenFormatter.FormatToken Contract.ThrowIfTrue(token.Kind = SyntaxKind.None OrElse token.Kind = SyntaxKind.EndOfFileToken) ' get previous token @@ -42,7 +42,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Indentation Dim spans = SpecializedCollections.SingletonEnumerable(TextSpan.FromBounds(previousToken.SpanStart, token.Span.End)) Dim formatter = VisualBasicSyntaxFormatting.Instance Dim result = formatter.GetFormattingResult(_root, spans, _options, _formattingRules, cancellationToken) - Return Task.FromResult(result.GetTextChanges(cancellationToken)) + Return result.GetTextChanges(cancellationToken) End Function End Class End Namespace diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Indentation/CSharpIndentationService.Indenter.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Indentation/CSharpIndentationService.Indenter.cs index c2dab9e09e49d..1a6a7b7260b11 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Indentation/CSharpIndentationService.Indenter.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/CSharp/Indentation/CSharpIndentationService.Indenter.cs @@ -26,11 +26,11 @@ protected override bool ShouldUseTokenIndenter(Indenter indenter, out SyntaxToke indenter.Rules, indenter.Root, indenter.LineToBeIndented, indenter.Options, out syntaxToken); protected override ISmartTokenFormatter CreateSmartTokenFormatter( - CompilationUnitSyntax root, TextLine lineToBeIndented, + CompilationUnitSyntax root, SourceText text, TextLine lineToBeIndented, IndentationOptions options, AbstractFormattingRule baseIndentationRule) { var rules = ImmutableArray.Create(baseIndentationRule).AddRange(CSharpSyntaxFormatting.Instance.GetDefaultFormattingRules()); - return new CSharpSmartTokenFormatter(options, rules, root); + return new CSharpSmartTokenFormatter(options, rules, root, text); } protected override IndentationResult? GetDesiredIndentationWorker(Indenter indenter, SyntaxToken? tokenOpt, SyntaxTrivia? triviaOpt) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Indentation/AbstractIndentationService.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Indentation/AbstractIndentationService.cs index d52c9660f2f08..2d9891a60ed5c 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Indentation/AbstractIndentationService.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Indentation/AbstractIndentationService.cs @@ -44,21 +44,19 @@ private Indenter GetIndenter(Document document, int lineNumber, IndentationOptio var syntaxFormatting = this.SyntaxFormatting; #if CODE_STYLE - var tree = document.GetSyntaxTreeAsync(cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken); - Contract.ThrowIfNull(tree); + var documentSyntax = ParsedDocument.CreateAsync(document, cancellationToken).AsTask().WaitAndGetResult_CanCallOnBackground(cancellationToken); #else - var tree = document.GetRequiredSyntaxTreeSynchronously(cancellationToken); + var documentSyntax = ParsedDocument.CreateSynchronously(document, cancellationToken); #endif - var sourceText = tree.GetText(cancellationToken); - var lineToBeIndented = sourceText.Lines[lineNumber]; + var lineToBeIndented = documentSyntax.Text.Lines[lineNumber]; #if CODE_STYLE var baseIndentationRule = NoOpFormattingRule.Instance; #else var workspace = document.Project.Solution.Workspace; var formattingRuleFactory = workspace.Services.GetRequiredService(); - var baseIndentationRule = formattingRuleFactory.CreateRule(document, lineToBeIndented.Start); + var baseIndentationRule = formattingRuleFactory.CreateRule(documentSyntax, lineToBeIndented.Start); #endif var formattingRules = ImmutableArray.Create( @@ -67,8 +65,8 @@ private Indenter GetIndenter(Document document, int lineNumber, IndentationOptio syntaxFormatting.GetDefaultFormattingRules()); var smartTokenFormatter = CreateSmartTokenFormatter( - (TSyntaxRoot)tree.GetRoot(cancellationToken), lineToBeIndented, options, baseIndentationRule); - return new Indenter(this, tree, formattingRules, options, lineToBeIndented, smartTokenFormatter, cancellationToken); + (TSyntaxRoot)documentSyntax.Root, documentSyntax.Text, lineToBeIndented, options, baseIndentationRule); + return new Indenter(this, documentSyntax.SyntaxTree, formattingRules, options, lineToBeIndented, smartTokenFormatter, cancellationToken); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/ParsedDocument.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/ParsedDocument.cs new file mode 100644 index 0000000000000..7abf80dd940ea --- /dev/null +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/ParsedDocument.cs @@ -0,0 +1,31 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis; + +internal readonly record struct ParsedDocument(DocumentId Id, SourceText Text, SyntaxNode Root) +{ + public SyntaxTree SyntaxTree => Root.SyntaxTree; + + public static async ValueTask CreateAsync(Document document, CancellationToken cancellationToken) + { + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return new ParsedDocument(document.Id, text, root); + } + +#if !CODE_STYLE + public static ParsedDocument CreateSynchronously(Document document, CancellationToken cancellationToken) + { + var text = document.GetTextSynchronously(cancellationToken); + var root = document.GetRequiredSyntaxRootSynchronously(cancellationToken); + return new ParsedDocument(document.Id, text, root); + } +#endif +} diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/SemanticDocument.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/SemanticDocument.cs index ac629ff1dcce1..80ef16b1d60c9 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/SemanticDocument.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/SemanticDocument.cs @@ -14,8 +14,8 @@ internal class SemanticDocument : SyntacticDocument { public readonly SemanticModel SemanticModel; - private SemanticDocument(Document document, SourceText text, SyntaxTree tree, SyntaxNode root, SemanticModel semanticModel) - : base(document, text, tree, root) + private SemanticDocument(Document document, SourceText text, SyntaxNode root, SemanticModel semanticModel) + : base(document, text, root) { this.SemanticModel = semanticModel; } @@ -25,7 +25,7 @@ private SemanticDocument(Document document, SourceText text, SyntaxTree tree, Sy var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - return new SemanticDocument(document, text, root.SyntaxTree, root, model); + return new SemanticDocument(document, text, root, model); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/SyntacticDocument.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/SyntacticDocument.cs index b0e34a9236143..75eaa3b7dab61 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/SyntacticDocument.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/Utilities/SyntacticDocument.cs @@ -2,10 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis @@ -14,25 +13,23 @@ internal class SyntacticDocument { public readonly Document Document; public readonly SourceText Text; - public readonly SyntaxTree SyntaxTree; public readonly SyntaxNode Root; - protected SyntacticDocument(Document document, SourceText text, SyntaxTree tree, SyntaxNode root) + protected SyntacticDocument(Document document, SourceText text, SyntaxNode root) { - this.Document = document; - this.Text = text; - this.SyntaxTree = tree; - this.Root = root; + Document = document; + Text = text; + Root = root; } - public Project Project => this.Document.Project; + public Project Project => Document.Project; + public SyntaxTree SyntaxTree => Root.SyntaxTree; - public static async Task CreateAsync( - Document document, CancellationToken cancellationToken) + public static async ValueTask CreateAsync(Document document, CancellationToken cancellationToken) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - return new SyntacticDocument(document, text, root.SyntaxTree, root); + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + return new SyntacticDocument(document, text, root); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems index f7e95fb1757ee..3e005c0bd0bd0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems @@ -69,6 +69,7 @@ + diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Indentation/VisualBasicIndentationService.Indenter.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Indentation/VisualBasicIndentationService.Indenter.vb index becb8781c9775..55dd1bc3bb5b1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Indentation/VisualBasicIndentationService.Indenter.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/VisualBasic/Indentation/VisualBasicIndentationService.Indenter.vb @@ -39,6 +39,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Indentation Protected Overrides Function CreateSmartTokenFormatter( root As CompilationUnitSyntax, + text As SourceText, lineToBeIndented As TextLine, options As IndentationOptions, baseIndentationRule As AbstractFormattingRule) As ISmartTokenFormatter diff --git a/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormattingService.vb b/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormattingService.vb index bd8f025bef48f..2f1c254754b9c 100644 --- a/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormattingService.vb +++ b/src/Workspaces/VisualBasic/Portable/Formatting/VisualBasicSyntaxFormattingService.vb @@ -21,15 +21,15 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Formatting Public Sub New() End Sub - Public Function ShouldFormatOnTypedCharacterAsync(document As Document, typedChar As Char, caretPosition As Integer, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ISyntaxFormattingService.ShouldFormatOnTypedCharacterAsync - Return Task.FromResult(False) + Public Function ShouldFormatOnTypedCharacter(document As ParsedDocument, typedChar As Char, caretPosition As Integer, cancellationToken As CancellationToken) As Boolean Implements ISyntaxFormattingService.ShouldFormatOnTypedCharacter + Return False End Function - Public Function GetFormattingChangesOnTypedCharacterAsync(document As Document, caretPosition As Integer, indentationOptions As IndentationOptions, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of TextChange)) Implements ISyntaxFormattingService.GetFormattingChangesOnTypedCharacterAsync + Public Function GetFormattingChangesOnTypedCharacter(document As ParsedDocument, caretPosition As Integer, indentationOptions As IndentationOptions, cancellationToken As CancellationToken) As ImmutableArray(Of TextChange) Implements ISyntaxFormattingService.GetFormattingChangesOnTypedCharacter Throw ExceptionUtilities.Unreachable End Function - Public Function GetFormattingChangesOnPasteAsync(document As Document, textSpan As TextSpan, options As SyntaxFormattingOptions, cancellationToken As CancellationToken) As Task(Of ImmutableArray(Of TextChange)) Implements ISyntaxFormattingService.GetFormattingChangesOnPasteAsync + Public Function GetFormattingChangesOnPaste(document As ParsedDocument, textSpan As TextSpan, options As SyntaxFormattingOptions, cancellationToken As CancellationToken) As ImmutableArray(Of TextChange) Implements ISyntaxFormattingService.GetFormattingChangesOnPaste Throw ExceptionUtilities.Unreachable End Function End Class From 73de4e98d3020c72a2eef479d267357c15332d80 Mon Sep 17 00:00:00 2001 From: David Wengier Date: Thu, 16 Jun 2022 11:55:53 +1000 Subject: [PATCH 38/42] Don't use exceptions for flow control (#61948) --- .../CSharpDecompiledSourceService.cs | 20 +++++++++++-------- .../IDecompiledSourceService.cs | 6 ++---- ...compilationMetadataAsSourceFileProvider.cs | 10 +++++++++- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs index f869fd78de105..cfdb436eefda5 100644 --- a/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs +++ b/src/EditorFeatures/CSharp/DecompiledSource/CSharpDecompiledSourceService.cs @@ -41,14 +41,19 @@ public CSharpDecompiledSourceService() { } - public async Task AddSourceToAsync(Document document, Compilation symbolCompilation, ISymbol symbol, MetadataReference metadataReference, string assemblyLocation, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) + public async Task AddSourceToAsync(Document document, Compilation symbolCompilation, ISymbol symbol, MetadataReference? metadataReference, string? assemblyLocation, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken) { // Get the name of the type the symbol is in var containingOrThis = symbol.GetContainingTypeOrThis(); var fullName = GetFullReflectionName(containingOrThis); // Decompile - document = PerformDecompilation(document, fullName, symbolCompilation, metadataReference, assemblyLocation); + var decompiledDocument = PerformDecompilation(document, fullName, symbolCompilation, metadataReference, assemblyLocation); + + if (decompiledDocument is null) + return null; + + document = decompiledDocument; document = await AddAssemblyInfoRegionAsync(document, symbol, cancellationToken).ConfigureAwait(false); @@ -74,7 +79,7 @@ public static async Task FormatDocumentAsync(Document document, Syntax return formattedDoc; } - private static Document PerformDecompilation(Document document, string fullName, Compilation compilation, MetadataReference? metadataReference, string assemblyLocation) + private static Document? PerformDecompilation(Document document, string fullName, Compilation compilation, MetadataReference? metadataReference, string? assemblyLocation) { var logger = new StringBuilder(); var resolver = new AssemblyResolver(compilation, logger); @@ -84,12 +89,11 @@ private static Document PerformDecompilation(Document document, string fullName, if (metadataReference is not null) file = resolver.TryResolve(metadataReference, PEStreamOptions.PrefetchEntireImage); - if (file is null && assemblyLocation is null) - { - throw new NotSupportedException(FeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret); - } + if (file is null && assemblyLocation is not null) + file = new PEFile(assemblyLocation, PEStreamOptions.PrefetchEntireImage); - file ??= new PEFile(assemblyLocation, PEStreamOptions.PrefetchEntireImage); + if (file is null) + return null; // Initialize a decompiler with default settings. var decompiler = new CSharpDecompiler(file, resolver, new DecompilerSettings()); diff --git a/src/Features/Core/Portable/DecompiledSource/IDecompiledSourceService.cs b/src/Features/Core/Portable/DecompiledSource/IDecompiledSourceService.cs index c5c79c91c484a..f93bf43260d7f 100644 --- a/src/Features/Core/Portable/DecompiledSource/IDecompiledSourceService.cs +++ b/src/Features/Core/Portable/DecompiledSource/IDecompiledSourceService.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Formatting; @@ -24,7 +22,7 @@ internal interface IDecompiledSourceService : ILanguageService /// The reference that contains the symbol /// The location of the implementation assembly to decompile /// To cancel document operations - /// The updated document - Task AddSourceToAsync(Document document, Compilation symbolCompilation, ISymbol symbol, MetadataReference metadataReference, string assemblyLocation, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken); + /// The updated document, or null if the decompilation could not be performed + Task AddSourceToAsync(Document document, Compilation symbolCompilation, ISymbol symbol, MetadataReference? metadataReference, string? assemblyLocation, SyntaxFormattingOptions formattingOptions, CancellationToken cancellationToken); } } diff --git a/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs b/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs index abafe25befaea..cd9cc69fd9f60 100644 --- a/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs +++ b/src/Features/Core/Portable/MetadataAsSource/DecompilationMetadataAsSourceFileProvider.cs @@ -92,7 +92,15 @@ public DecompilationMetadataAsSourceFileProvider(IImplementationAssemblyLookupSe if (decompiledSourceService != null) { - temporaryDocument = await decompiledSourceService.AddSourceToAsync(temporaryDocument, compilation, symbol, refInfo.metadataReference, refInfo.assemblyLocation, options.GenerationOptions.CleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); + var decompilationDocument = await decompiledSourceService.AddSourceToAsync(temporaryDocument, compilation, symbol, refInfo.metadataReference, refInfo.assemblyLocation, options.GenerationOptions.CleanupOptions.FormattingOptions, cancellationToken).ConfigureAwait(false); + if (decompilationDocument is not null) + { + temporaryDocument = decompilationDocument; + } + else + { + useDecompiler = false; + } } else { From 97a528f2db3d9bca455b24c6b39c2a56e3434b44 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Wed, 15 Jun 2022 18:59:31 -0700 Subject: [PATCH 39/42] Use 'Clear' to empty a CWT when on .net core --- .../DiagnosticIncrementalAnalyzer.CompilationManager.cs | 8 +++++--- .../Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs index 40a58f752954a..577abb2d62d3b 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.CompilationManager.cs @@ -61,10 +61,12 @@ private void ClearCompilationsWithAnalyzersCache() // we basically eagarly clear the cache on some known changes // to let CompilationWithAnalyzer go. - // we create new conditional weak table every time, it turns out - // only way to clear ConditionalWeakTable is re-creating it. - // also, conditional weak table has a leak - https://github.com/dotnet/coreclr/issues/665 + // we create new conditional weak table every time netstandard as that's the only way it has to clear it. +#if NETSTANDARD _projectCompilationsWithAnalyzers = new ConditionalWeakTable(); +#else + _projectCompilationsWithAnalyzers.Clear(); +#endif } [Conditional("DEBUG")] diff --git a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs index e87c6435602ac..89bf1b1f00c9d 100644 --- a/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs +++ b/src/Features/LanguageServer/Protocol/Features/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.cs @@ -33,7 +33,12 @@ internal partial class DiagnosticIncrementalAnalyzer : IIncrementalAnalyzer private readonly StateManager _stateManager; private readonly InProcOrRemoteHostAnalyzerRunner _diagnosticAnalyzerRunner; private readonly IDocumentTrackingService _documentTrackingService; + +#if NETSTANDARD private ConditionalWeakTable _projectCompilationsWithAnalyzers = new(); +#else + private readonly ConditionalWeakTable _projectCompilationsWithAnalyzers = new(); +#endif internal DiagnosticAnalyzerService AnalyzerService { get; } internal Workspace Workspace { get; } From e4d1ce6e8a244f68ac87929b0a1c09c6c39b4cdb Mon Sep 17 00:00:00 2001 From: David Barbet Date: Thu, 16 Jun 2022 10:00:54 -0700 Subject: [PATCH 40/42] Add support for additional file diagnostics in workspace pull (#61930) * Add support for additional file diagnostics in workspace pull * fix formatting * Fix more formatting --- .../Handlers/CodeActions/CodeActionHelpers.cs | 2 +- .../TestUtilities/IsExternalInit.cs | 11 ++ ...rverProtocolTests.InitializationOptions.cs | 22 ++++ .../AbstractLanguageServerProtocolTests.cs | 94 +++++++-------- .../Protocol/Extensions/Extensions.cs | 13 +- .../AbstractPullDiagnosticHandler.cs | 25 +++- .../DocumentPullDiagnosticHandler.cs | 6 + ...erimentalDocumentPullDiagnosticsHandler.cs | 16 +-- ...rimentalWorkspacePullDiagnosticsHandler.cs | 24 ++-- .../WorkspacePullDiagnosticHandler.cs | 26 ++-- .../CodeActions/CodeActionsTests.cs | 16 ++- .../Definitions/GoToDefinitionTests.cs | 10 +- .../AbstractPullDiagnosticTestsBase.cs | 54 ++++----- .../AdditionalFileDiagnosticsTests.cs | 113 ++++++++++++++++++ .../Diagnostics/PullDiagnosticTests.cs | 22 ++-- .../WorkspaceProjectDiagnosticsTests.cs | 11 +- .../ProtocolUnitTests/Hover/HoverTests.cs | 2 +- .../LspMiscellaneousFilesWorkspaceTests.cs | 12 +- .../OnAutoInsert/OnAutoInsertTests.cs | 4 +- .../FindAllReferencesHandlerTests.cs | 2 +- .../Workspaces/LspWorkspaceManagerTests.cs | 6 +- 21 files changed, 335 insertions(+), 156 deletions(-) create mode 100644 src/EditorFeatures/TestUtilities/IsExternalInit.cs create mode 100644 src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs create mode 100644 src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs diff --git a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs b/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs index 571cdda77f6b0..3a957e6848bea 100644 --- a/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs +++ b/src/EditorFeatures/Core/LanguageServer/Handlers/CodeActions/CodeActionHelpers.cs @@ -154,7 +154,7 @@ static VSInternalCodeAction[] GenerateNestedVSCodeActions( static LSP.Diagnostic[]? GetApplicableDiagnostics(LSP.CodeActionContext context, IUnifiedSuggestedAction action) { - if (action is UnifiedCodeFixSuggestedAction codeFixAction) + if (action is UnifiedCodeFixSuggestedAction codeFixAction && context.Diagnostics != null) { // Associate the diagnostics from the request that match the diagnostic fixed by the code action by ID. // The request diagnostics are already restricted to the code fix location by the request. diff --git a/src/EditorFeatures/TestUtilities/IsExternalInit.cs b/src/EditorFeatures/TestUtilities/IsExternalInit.cs new file mode 100644 index 0000000000000..a45663ee0a382 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/IsExternalInit.cs @@ -0,0 +1,11 @@ +// 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. + +namespace System.Runtime.CompilerServices +{ + // Used to compile against C# 9 in a netstandard2.0 + internal class IsExternalInit + { + } +} diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs new file mode 100644 index 0000000000000..7011e81bbcfc3 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.InitializationOptions.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.Options; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Roslyn.Test.Utilities +{ + public abstract partial class AbstractLanguageServerProtocolTests + { + internal record struct InitializationOptions() + { + internal string[] SourceGeneratedMarkups { get; init; } = Array.Empty(); + internal LSP.ClientCapabilities ClientCapabilities { get; init; } = new LSP.ClientCapabilities(); + internal WellKnownLspServerKinds ServerKind { get; init; } = WellKnownLspServerKinds.AlwaysActiveVSLspServer; + internal Action? OptionUpdater { get; init; } = null; + } + } +} diff --git a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs index 69035b80e0f89..4ae7cd6b4b0fb 100644 --- a/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs +++ b/src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs @@ -99,6 +99,9 @@ protected class OrderLocations : Comparer protected virtual TestComposition Composition => s_composition; + private protected virtual TestAnalyzerReferenceByLanguage TestAnalyzerReferences + => new(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap()); + protected static LSP.ClientCapabilities CapabilitiesWithVSExtensions => new LSP.VSInternalClientCapabilities { SupportsVisualStudioExtensions = true }; /// @@ -282,50 +285,42 @@ protected static LSP.TextEdit GenerateTextEdit(string newText, int startLine, in private protected static CodeActionResolveData CreateCodeActionResolveData(string uniqueIdentifier, LSP.Location location, IEnumerable? customTags = null) => new CodeActionResolveData(uniqueIdentifier, customTags.ToImmutableArrayOrEmpty(), location.Range, CreateTextDocumentIdentifier(location.Uri)); - /// - /// Creates an LSP server backed by a workspace instance with a solution containing the markup. - /// - protected Task CreateTestLspServerAsync(string markup, LSP.ClientCapabilities? clientCapabilities = null) - => CreateTestLspServerAsync(new string[] { markup }, Array.Empty(), LanguageNames.CSharp, clientCapabilities); - - private protected Task CreateVisualBasicTestLspServerAsync(string markup, LSP.ClientCapabilities? clientCapabilities = null, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) - => CreateTestLspServerAsync(new string[] { markup }, Array.Empty(), LanguageNames.VisualBasic, clientCapabilities, serverKind); - - protected Task CreateMultiProjectLspServerAsync(string xmlMarkup, LSP.ClientCapabilities? clientCapabilities = null) - => CreateTestLspServerAsync(TestWorkspace.Create(xmlMarkup, composition: Composition), clientCapabilities, WellKnownLspServerKinds.AlwaysActiveVSLspServer); + private protected Task CreateTestLspServerAsync(string markup, LSP.ClientCapabilities clientCapabilities) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.CSharp, new InitializationOptions { ClientCapabilities = clientCapabilities }); - /// - /// Creates an LSP server backed by a workspace instance with a solution containing the specified documents. - /// - protected Task CreateTestLspServerAsync(string[] markups, LSP.ClientCapabilities? clientCapabilities = null) - => CreateTestLspServerAsync(markups, Array.Empty(), LanguageNames.CSharp, clientCapabilities); + private protected Task CreateTestLspServerAsync(string markup, InitializationOptions? initializationOptions = null) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.CSharp, initializationOptions); - private protected Task CreateTestLspServerAsync(string markup, LSP.ClientCapabilities clientCapabilities, WellKnownLspServerKinds serverKind) - => CreateTestLspServerAsync(new string[] { markup }, Array.Empty(), LanguageNames.CSharp, clientCapabilities, serverKind); + private protected Task CreateTestLspServerAsync(string[] markups, InitializationOptions? initializationOptions = null) + => CreateTestLspServerAsync(markups, LanguageNames.CSharp, initializationOptions); - /// - /// Creates an LSP server backed by a workspace instance with a solution containing the specified documents. - /// - protected Task CreateTestLspServerAsync(string[] markups, string[] sourceGeneratedMarkups, LSP.ClientCapabilities? clientCapabilities = null) - => CreateTestLspServerAsync(markups, sourceGeneratedMarkups, LanguageNames.CSharp, clientCapabilities); + private protected Task CreateVisualBasicTestLspServerAsync(string markup, InitializationOptions? initializationOptions = null) + => CreateTestLspServerAsync(new string[] { markup }, LanguageNames.VisualBasic, initializationOptions); - private Task CreateTestLspServerAsync(string[] markups, string[] sourceGeneratedMarkups, string languageName, LSP.ClientCapabilities? clientCapabilities, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) + private Task CreateTestLspServerAsync(string[] markups, string languageName, InitializationOptions? initializationOptions) { + var lspOptions = initializationOptions ?? new InitializationOptions(); var exportProvider = Composition.ExportProviderFactory.CreateExportProvider(); var workspaceConfigurationService = exportProvider.GetExportedValue(); workspaceConfigurationService.Options = new WorkspaceConfigurationOptions(EnableOpeningSourceGeneratedFiles: true); + if (lspOptions.OptionUpdater != null) + { + var globalOptions = exportProvider.GetExportedValue(); + lspOptions.OptionUpdater(globalOptions); + } + var workspace = languageName switch { - LanguageNames.CSharp => TestWorkspace.CreateCSharp(markups, sourceGeneratedMarkups, exportProvider: exportProvider), - LanguageNames.VisualBasic => TestWorkspace.CreateVisualBasic(markups, sourceGeneratedMarkups, exportProvider: exportProvider), + LanguageNames.CSharp => TestWorkspace.CreateCSharp(markups, lspOptions.SourceGeneratedMarkups, exportProvider: exportProvider), + LanguageNames.VisualBasic => TestWorkspace.CreateVisualBasic(markups, lspOptions.SourceGeneratedMarkups, exportProvider: exportProvider), _ => throw new ArgumentException($"language name {languageName} is not valid for a test workspace"), }; - return CreateTestLspServerAsync(workspace, clientCapabilities, serverKind); + return CreateTestLspServerAsync(workspace, lspOptions); } - private static async Task CreateTestLspServerAsync(TestWorkspace workspace, LSP.ClientCapabilities? clientCapabilities, WellKnownLspServerKinds serverKind) + private async Task CreateTestLspServerAsync(TestWorkspace workspace, InitializationOptions initializationOptions) { var solution = workspace.CurrentSolution; @@ -346,6 +341,7 @@ private static async Task CreateTestLspServerAsync(TestWorkspace solution = solution.WithProjectFilePath(project.Id, GetDocumentFilePathFromName(project.FilePath)); } + solution = solution.WithAnalyzerReferences(new[] { TestAnalyzerReferences }); workspace.ChangeSolution(solution); // Important: We must wait for workspace creation operations to finish. @@ -353,22 +349,30 @@ private static async Task CreateTestLspServerAsync(TestWorkspace // created by the initial test steps. This can interfere with the expected test state. await WaitForWorkspaceOperationsAsync(workspace); - return await TestLspServer.CreateAsync(workspace, clientCapabilities ?? new LSP.ClientCapabilities(), serverKind); + return await TestLspServer.CreateAsync(workspace, initializationOptions); } private protected async Task CreateXmlTestLspServerAsync( string xmlContent, string? workspaceKind = null, - LSP.ClientCapabilities? clientCapabilities = null, - WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) + InitializationOptions? initializationOptions = null) { - var workspace = TestWorkspace.Create(XElement.Parse(xmlContent), openDocuments: false, composition: Composition, workspaceKind: workspaceKind); + var lspOptions = initializationOptions ?? new InitializationOptions(); + var exportProvider = Composition.ExportProviderFactory.CreateExportProvider(); + if (lspOptions.OptionUpdater != null) + { + var globalOptions = exportProvider.GetExportedValue(); + lspOptions.OptionUpdater(globalOptions); + } + + var workspace = TestWorkspace.Create(XElement.Parse(xmlContent), openDocuments: false, exportProvider: exportProvider, workspaceKind: workspaceKind); + workspace.TryApplyChanges(workspace.CurrentSolution.WithAnalyzerReferences(new[] { TestAnalyzerReferences })); // Important: We must wait for workspace creation operations to finish. // Otherwise we could have a race where workspace change events triggered by creation are changing the state // created by the initial test steps. This can interfere with the expected test state. await WaitForWorkspaceOperationsAsync(workspace); - return await TestLspServer.CreateAsync(workspace, clientCapabilities ?? new LSP.ClientCapabilities(), serverKind); + return await TestLspServer.CreateAsync(workspace, lspOptions); } /// @@ -472,7 +476,7 @@ private static LSP.DidCloseTextDocumentParams CreateDidCloseTextDocumentParams(U } }; - public sealed class TestLspServer : IDisposable + internal sealed class TestLspServer : IDisposable { public readonly TestWorkspace TestWorkspace; private readonly Dictionary> _locations; @@ -534,14 +538,14 @@ private static JsonMessageFormatter CreateJsonMessageFormatter() return messageFormatter; } - internal static async Task CreateAsync(TestWorkspace testWorkspace, LSP.ClientCapabilities clientCapabilities, WellKnownLspServerKinds serverKind) + internal static async Task CreateAsync(TestWorkspace testWorkspace, InitializationOptions initializationOptions) { var locations = await GetAnnotatedLocationsAsync(testWorkspace, testWorkspace.CurrentSolution); - var server = new TestLspServer(testWorkspace, locations, clientCapabilities, serverKind); + var server = new TestLspServer(testWorkspace, locations, initializationOptions.ClientCapabilities, initializationOptions.ServerKind); await server.ExecuteRequestAsync(LSP.Methods.InitializeName, new LSP.InitializeParams { - Capabilities = clientCapabilities, + Capabilities = initializationOptions.ClientCapabilities, }, CancellationToken.None); return server; @@ -639,22 +643,6 @@ public Task CloseDocumentAsync(Uri documentUri) public Solution GetCurrentSolution() => TestWorkspace.CurrentSolution; - internal void InitializeDiagnostics(BackgroundAnalysisScope scope, DiagnosticMode diagnosticMode, TestAnalyzerReferenceByLanguage references) - { - TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), scope); - TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), scope); - TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript), scope); - TestWorkspace.GlobalOptions.SetGlobalOption(new OptionKey(InternalDiagnosticsOptions.NormalDiagnosticMode), diagnosticMode); - - TestWorkspace.TryApplyChanges(TestWorkspace.CurrentSolution.WithAnalyzerReferences(new[] { references })); - - var registrationService = TestWorkspace.Services.GetRequiredService(); - registrationService.Register(TestWorkspace); - - var diagnosticService = (DiagnosticService)TestWorkspace.ExportProvider.GetExportedValue(); - diagnosticService.Register(new TestHostDiagnosticUpdateSource(TestWorkspace)); - } - internal async Task WaitForDiagnosticsAsync() { var listenerProvider = TestWorkspace.GetService(); diff --git a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs index 90f2a59eece6a..7bebe2e83649e 100644 --- a/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs +++ b/src/Features/LanguageServer/Protocol/Extensions/Extensions.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.FindUsages; @@ -36,8 +37,8 @@ public static ImmutableArray GetDocuments(this Solution solution, Uri { var documentIds = GetDocumentIds(solution, documentUri); - var documents = documentIds.SelectAsArray(id => solution.GetRequiredDocument(id)); - + // We don't call GetRequiredDocument here as the id could be referring to an additional document. + var documents = documentIds.Select(solution.GetDocument).WhereNotNull().ToImmutableArray(); return documents; } @@ -103,6 +104,14 @@ public static Document FindDocumentInProjectContext(this ImmutableArray solution.Projects.Where(project => project.FilePath == projectIdentifier.Uri.LocalPath).SingleOrDefault(); + public static TextDocument? GetAdditionalDocument(this Solution solution, TextDocumentIdentifier documentIdentifier) + { + var documentIds = GetDocumentIds(solution, documentIdentifier.Uri); + + // We don't call GetRequiredAdditionalDocument as the id could be referring to a regular document. + return documentIds.Select(solution.GetAdditionalDocument).WhereNotNull().SingleOrDefault(); + } + public static async Task GetPositionFromLinePositionAsync(this TextDocument document, LinePosition linePosition, CancellationToken cancellationToken) { var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs index c5cc1dcec2493..f0ec470cf56cb 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/AbstractPullDiagnosticHandler.cs @@ -89,10 +89,19 @@ protected AbstractPullDiagnosticHandler( protected abstract ValueTask> GetOrderedDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken); /// - /// Creates the instance we'll report back to clients to let them know our - /// progress. Subclasses can fill in data specific to their needs as appropriate. + /// Creates the appropriate LSP type to report a new set of diagnostics and resultId. /// - protected abstract TReport CreateReport(TextDocumentIdentifier identifier, LSP.Diagnostic[]? diagnostics, string? resultId); + protected abstract TReport CreateReport(TextDocumentIdentifier identifier, LSP.Diagnostic[] diagnostics, string resultId); + + /// + /// Creates the appropriate LSP type to report unchanged diagnostics. + /// + protected abstract TReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId); + + /// + /// Creates the appropriate LSP type to report a removed file. + /// + protected abstract TReport CreateRemovedReport(TextDocumentIdentifier identifier); protected abstract TReturn? CreateReturn(BufferedProgress progress); @@ -157,7 +166,7 @@ protected AbstractPullDiagnosticHandler( // same-result-id) response to the client as that means they should just preserve the current // diagnostics they have for this file. var previousParams = documentToPreviousDiagnosticParams[diagnosticSource.GetId()]; - progress.Report(CreateReport(previousParams.TextDocument, diagnostics: null, previousParams.PreviousResultId)); + progress.Report(CreateUnchangedReport(previousParams.TextDocument, previousParams.PreviousResultId)); } } @@ -209,6 +218,12 @@ private static Dictionary GetIdToPrevio return new ProjectOrDocumentId(project.Id); } + var additionalDocument = solution.GetAdditionalDocument(textDocumentIdentifier); + if (additionalDocument != null) + { + return new ProjectOrDocumentId(additionalDocument.Id); + } + return null; } } @@ -254,7 +269,7 @@ private void HandleRemovedDocuments(RequestContext context, ImmutableArray CreateReport(identifier, diagnostics: null, resultId: null); + + protected override VSInternalDiagnosticReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) + => CreateReport(identifier, diagnostics: null, resultId); + protected override ImmutableArray? GetPreviousResults(VSInternalDocumentDiagnosticsParams diagnosticsParams) { if (diagnosticsParams.PreviousResultId != null && diagnosticsParams.TextDocument != null) diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs index 36094e977023f..09e8281c94063 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalDocumentPullDiagnosticsHandler.cs @@ -42,14 +42,14 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData) return ConvertTags(diagnosticData, potentialDuplicate: false); } - protected override DocumentDiagnosticPartialReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - { - // We will only report once for document pull, so we only need to return the first literal send = DocumentDiagnosticReport. - var report = diagnostics == null - ? new DocumentDiagnosticReport(new UnchangedDocumentDiagnosticReport(resultId)) - : new DocumentDiagnosticReport(new FullDocumentDiagnosticReport(resultId, diagnostics)); - return report; - } + protected override DocumentDiagnosticPartialReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[] diagnostics, string resultId) + => new DocumentDiagnosticReport(new FullDocumentDiagnosticReport(resultId, diagnostics)); + + protected override DocumentDiagnosticPartialReport CreateRemovedReport(TextDocumentIdentifier identifier) + => new DocumentDiagnosticReport(new FullDocumentDiagnosticReport(resultId: null, Array.Empty())); + + protected override DocumentDiagnosticPartialReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) + => new DocumentDiagnosticReport(new UnchangedDocumentDiagnosticReport(resultId)); protected override DocumentDiagnosticReport? CreateReturn(BufferedProgress progress) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs index 468405df55314..b330cd0387676 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/Experimental/ExperimentalWorkspacePullDiagnosticsHandler.cs @@ -37,13 +37,23 @@ protected override DiagnosticTag[] ConvertTags(DiagnosticData diagnosticData) return ConvertTags(diagnosticData, potentialDuplicate: false); } - protected override WorkspaceDiagnosticReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[]? diagnostics, string? resultId) - { - var itemToReport = diagnostics == null - ? new WorkspaceDocumentDiagnosticReport(new WorkspaceUnchangedDocumentDiagnosticReport(identifier.Uri, resultId, version: null)) - : new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, diagnostics, version: null, resultId)); - return new WorkspaceDiagnosticReport(new[] { itemToReport }); - } + protected override WorkspaceDiagnosticReport CreateReport(TextDocumentIdentifier identifier, VisualStudio.LanguageServer.Protocol.Diagnostic[] diagnostics, string resultId) + => new WorkspaceDiagnosticReport(new[] + { + new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, diagnostics, version: null, resultId)) + }); + + protected override WorkspaceDiagnosticReport CreateRemovedReport(TextDocumentIdentifier identifier) + => new WorkspaceDiagnosticReport(new[] + { + new WorkspaceDocumentDiagnosticReport(new WorkspaceFullDocumentDiagnosticReport(identifier.Uri, Array.Empty(), version: null, resultId: null)) + }); + + protected override WorkspaceDiagnosticReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) + => new WorkspaceDiagnosticReport(new[] + { + new WorkspaceDocumentDiagnosticReport(new WorkspaceUnchangedDocumentDiagnosticReport(identifier.Uri, resultId, version: null)) + }); protected override WorkspaceDiagnosticReport? CreateReturn(BufferedProgress progress) { diff --git a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs index dc93d10e05e1a..a7c3a714d8e5e 100644 --- a/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs +++ b/src/Features/LanguageServer/Protocol/Handler/Diagnostics/WorkspacePullDiagnosticHandler.cs @@ -49,6 +49,12 @@ protected override VSInternalWorkspaceDiagnosticReport CreateReport(TextDocument Identifier = WorkspaceDiagnosticIdentifier, }; + protected override VSInternalWorkspaceDiagnosticReport CreateRemovedReport(TextDocumentIdentifier identifier) + => CreateReport(identifier, diagnostics: null, resultId: null); + + protected override VSInternalWorkspaceDiagnosticReport CreateUnchangedReport(TextDocumentIdentifier identifier, string resultId) + => CreateReport(identifier, diagnostics: null, resultId); + protected override ImmutableArray? GetPreviousResults(VSInternalWorkspaceDiagnosticsParams diagnosticsParams) => diagnosticsParams.PreviousResults?.Where(d => d.PreviousResultId != null).Select(d => new PreviousPullResult(d.PreviousResultId!, d.TextDocument!)).ToImmutableArray(); @@ -118,11 +124,11 @@ async Task AddDocumentsAndProject(Project? project, ImmutableArray suppo } var isFSAOn = globalOptions.IsFullSolutionAnalysisEnabled(project.Language); - var documents = ImmutableArray.Empty; + var documents = ImmutableArray.Empty; // If FSA is on, then add all the documents in the project. Other analysis scopes are handled by the document pull handler. if (isFSAOn) { - documents = documents.AddRange(project.Documents); + documents = documents.AddRange(project.Documents).AddRange(project.AdditionalDocuments); } // If all features are enabled for source generated documents, make sure they are included when FSA is on or a file in the project is open. @@ -188,7 +194,7 @@ public async Task> GetDiagnosticsAsync( } } - private record struct WorkspaceDocumentDiagnosticSource(Document Document) : IDiagnosticSource + private record struct WorkspaceDocumentDiagnosticSource(TextDocument Document) : IDiagnosticSource { public ProjectOrDocumentId GetId() => new(Document.Id); @@ -202,18 +208,18 @@ public async Task> GetDiagnosticsAsync( DiagnosticMode diagnosticMode, CancellationToken cancellationToken) { - if (Document is not SourceGeneratedDocument) + if (Document is SourceGeneratedDocument sourceGeneratedDocument) { - // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of diagnostics for this document - // including those reported as a compilation end diagnostic. These are not included in document pull (uses GetDiagnosticsForSpan) due to cost. - // However we can include them as a part of workspace pull when FSA is on. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync(Document.Project.Solution, Document.Project.Id, Document.Id, cancellationToken: cancellationToken).ConfigureAwait(false); + // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. + var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(sourceGeneratedDocument, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); return documentDiagnostics; } else { - // Unfortunately GetDiagnosticsForIdsAsync returns nothing for source generated documents. - var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForSpanAsync(Document, range: null, cancellationToken: cancellationToken).ConfigureAwait(false); + // We call GetDiagnosticsForIdsAsync as we want to ensure we get the full set of diagnostics for this document + // including those reported as a compilation end diagnostic. These are not included in document pull (uses GetDiagnosticsForSpan) due to cost. + // However we can include them as a part of workspace pull when FSA is on. + var documentDiagnostics = await diagnosticAnalyzerService.GetDiagnosticsForIdsAsync(Document.Project.Solution, Document.Project.Id, Document.Id, cancellationToken: cancellationToken).ConfigureAwait(false); return documentDiagnostics; } } diff --git a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs index 4ce1225326c69..15cab83dbddcf 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/CodeActions/CodeActionsTests.cs @@ -75,7 +75,7 @@ void M() int {|caret:|}i = 1; } }"; - using var testLspServer = await CreateTestLspServerAsync(markup); + using var testLspServer = await CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions); var caretLocation = testLspServer.GetLocations("caret").Single(); var expected = CreateCodeAction( @@ -86,14 +86,16 @@ void M() FeaturesResources.Introduce_constant + '|' + string.Format(FeaturesResources.Introduce_constant_for_0, "1"), caretLocation), priority: VSInternalPriorityLevel.Normal, - groupName: "Roslyn2", + groupName: "Roslyn3", applicableRange: new LSP.Range { Start = new Position { Line = 4, Character = 12 }, End = new Position { Line = 4, Character = 12 } }, diagnostics: null); var results = await RunGetCodeActionsAsync(testLspServer, CreateCodeActionParams(caretLocation)); - var introduceConstant = results[0].Children.FirstOrDefault( - r => ((JObject)r.Data).ToObject().UniqueIdentifier == FeaturesResources.Introduce_constant - + '|' + string.Format(FeaturesResources.Introduce_constant_for_0, "1")); + + var topLevelAction = Assert.Single(results.Where(action => action.Title == FeaturesResources.Introduce_constant)); + var expectedChildActionTitle = FeaturesResources.Introduce_constant + '|' + string.Format(FeaturesResources.Introduce_constant_for_0, "1"); + var introduceConstant = topLevelAction.Children.FirstOrDefault( + r => ((JObject)r.Data).ToObject().UniqueIdentifier == expectedChildActionTitle); AssertJsonEquals(expected, introduceConstant); } @@ -201,10 +203,6 @@ void M() }"; using var testLspServer = await CreateTestLspServerAsync(markup); - testLspServer.InitializeDiagnostics(BackgroundAnalysisScope.ActiveFile, DiagnosticMode.Default, - new TestAnalyzerReferenceByLanguage(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap())); - await testLspServer.WaitForDiagnosticsAsync(); - var caret = testLspServer.GetLocations("caret").Single(); var codeActionParams = new LSP.CodeActionParams { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs index 5273013540e74..8cfc336fe976f 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Definitions/GoToDefinitionTests.cs @@ -125,23 +125,23 @@ public async Task TestGotoDefinitionCrossLanguage() { var markup = @" - - + + public class {|definition:A|} { } - + Definition - + Class C Dim a As {|caret:A|} End Class "; - using var testLspServer = await CreateMultiProjectLspServerAsync(markup); + using var testLspServer = await CreateXmlTestLspServerAsync(markup); var results = await RunGotoDefinitionAsync(testLspServer, testLspServer.GetLocations("caret").Single()); AssertLocationsEqual(testLspServer.GetLocations("definition"), results); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs index db4cd2b93c0ee..dba7b0c11c168 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AbstractPullDiagnosticTestsBase.cs @@ -9,8 +9,10 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.LanguageServer.Handler; using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics.Experimental; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; @@ -27,8 +29,8 @@ namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics public abstract class AbstractPullDiagnosticTestsBase : AbstractLanguageServerProtocolTests { - protected virtual ImmutableDictionary> TestAnalyzers => DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap() - .Add(InternalLanguageNames.TypeScript, ImmutableArray.Create(new MockTypescriptDiagnosticAnalyzer())); + private protected override TestAnalyzerReferenceByLanguage TestAnalyzerReferences => new(DiagnosticExtensions.GetCompilerDiagnosticAnalyzersMap() + .Add(InternalLanguageNames.TypeScript, ImmutableArray.Create(new MockTypescriptDiagnosticAnalyzer()))); protected override TestComposition Composition => base.Composition.AddParts(typeof(MockTypescriptDiagnosticAnalyzer)); @@ -210,36 +212,34 @@ static DocumentDiagnosticParams CreateProposedDocumentDiagnosticParams( } private protected Task CreateTestWorkspaceWithDiagnosticsAsync(string markup, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) - => CreateTestWorkspaceWithDiagnosticsAsync(markup, scope, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push, useVSDiagnostics); - - private protected async Task CreateTestWorkspaceFromXmlAsync(string xmlMarkup, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) - { - var testLspServer = await CreateXmlTestLspServerAsync(xmlMarkup, clientCapabilities: useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities()); - InitializeDiagnostics(scope, testLspServer, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); - return testLspServer; - } - - private protected async Task CreateTestWorkspaceWithDiagnosticsAsync(string markup, BackgroundAnalysisScope scope, DiagnosticMode mode, bool useVSDiagnostics, WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer) - { - var testLspServer = await CreateTestLspServerAsync(markup, useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities(), serverKind); - InitializeDiagnostics(scope, testLspServer, mode); - return testLspServer; - } + => CreateTestLspServerAsync(markup, GetInitializationOptions(scope, useVSDiagnostics, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push)); private protected Task CreateTestWorkspaceWithDiagnosticsAsync(string[] markups, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) - => CreateTestWorkspaceWithDiagnosticsAsync(markups, Array.Empty(), scope, useVSDiagnostics, pullDiagnostics); + => CreateTestLspServerAsync(markups, GetInitializationOptions(scope, useVSDiagnostics, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push)); - private protected async Task CreateTestWorkspaceWithDiagnosticsAsync(string[] markups, string[] sourceGeneratedMarkups, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) - { - var testLspServer = await CreateTestLspServerAsync(markups, sourceGeneratedMarkups, useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities()); - InitializeDiagnostics(scope, testLspServer, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push); - return testLspServer; - } + private protected Task CreateTestWorkspaceFromXmlAsync(string xmlMarkup, BackgroundAnalysisScope scope, bool useVSDiagnostics, bool pullDiagnostics = true) + => CreateXmlTestLspServerAsync(xmlMarkup, initializationOptions: GetInitializationOptions(scope, useVSDiagnostics, pullDiagnostics ? DiagnosticMode.Pull : DiagnosticMode.Push)); - private void InitializeDiagnostics(BackgroundAnalysisScope scope, TestLspServer testLspServer, DiagnosticMode diagnosticMode) + private protected static InitializationOptions GetInitializationOptions( + BackgroundAnalysisScope scope, + bool useVSDiagnostics, + DiagnosticMode mode, + WellKnownLspServerKinds serverKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer, + string[]? sourceGeneratedMarkups = null) { - var analyzerReference = new TestAnalyzerReferenceByLanguage(TestAnalyzers); - testLspServer.InitializeDiagnostics(scope, diagnosticMode, analyzerReference); + return new InitializationOptions + { + ClientCapabilities = useVSDiagnostics ? CapabilitiesWithVSExtensions : new LSP.ClientCapabilities(), + OptionUpdater = (globalOptions) => + { + globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp), scope); + globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic), scope); + globalOptions.SetGlobalOption(new OptionKey(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, InternalLanguageNames.TypeScript), scope); + globalOptions.SetGlobalOption(new OptionKey(InternalDiagnosticsOptions.NormalDiagnosticMode), mode); + }, + ServerKind = serverKind, + SourceGeneratedMarkups = sourceGeneratedMarkups ?? Array.Empty() + }; } /// diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs new file mode 100644 index 0000000000000..1fdaf6583bbae --- /dev/null +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/AdditionalFileDiagnosticsTests.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.SolutionCrawler; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; +public class AdditionalFileDiagnosticsTests : AbstractPullDiagnosticTestsBase +{ + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsReportsAdditionalFileDiagnostic(bool useVSDiagnostics) + { + var workspaceXml = +@$" + + + + +"; + + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + Assert.Equal(3, results.Length); + + Assert.Empty(results[0].Diagnostics); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(@"C:\Test.txt", results[1].Uri.LocalPath); + Assert.Empty(results[2].Diagnostics); + + // Asking again should give us back an unchanged diagnostic. + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + Assert.Null(results2[0].Diagnostics); + Assert.Null(results2[1].Diagnostics); + Assert.Equal(results[1].ResultId, results2[1].ResultId); + Assert.Null(results2[2].Diagnostics); + } + + [Theory, CombinatorialData] + public async Task TestWorkspaceDiagnosticsWithRemovedAdditionalFile(bool useVSDiagnostics) + { + var workspaceXml = +@$" + + + + +"; + + using var testLspServer = await CreateTestWorkspaceFromXmlAsync(workspaceXml, BackgroundAnalysisScope.FullSolution, useVSDiagnostics); + + var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); + Assert.Equal(3, results.Length); + + Assert.Empty(results[0].Diagnostics); + Assert.Equal(MockAdditionalFileDiagnosticAnalyzer.Id, results[1].Diagnostics.Single().Code); + Assert.Equal(@"C:\Test.txt", results[1].Uri.LocalPath); + Assert.Empty(results[2].Diagnostics); + + var initialSolution = testLspServer.GetCurrentSolution(); + var newSolution = initialSolution.RemoveAdditionalDocument(initialSolution.Projects.Single().AdditionalDocumentIds.Single()); + await testLspServer.TestWorkspace.ChangeSolutionAsync(newSolution); + + var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); + Assert.Equal(3, results2.Length); + + // The first report is the report for the removed additional file. + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results2[0].Diagnostics); + Assert.Null(results2[0].ResultId); + + // The other files should have new results since the solution changed. + Assert.Empty(results2[1].Diagnostics); + Assert.NotNull(results2[1].ResultId); + Assert.Empty(results2[2].Diagnostics); + Assert.NotNull(results2[2].ResultId); + } + + protected override TestComposition Composition => base.Composition.AddParts(typeof(MockAdditionalFileDiagnosticAnalyzer)); + + private protected override TestAnalyzerReferenceByLanguage TestAnalyzerReferences => new(ImmutableDictionary.Create>() + .Add(LanguageNames.CSharp, ImmutableArray.Create(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp), new MockAdditionalFileDiagnosticAnalyzer()))); + + [DiagnosticAnalyzer(LanguageNames.CSharp), PartNotDiscoverable] + private class MockAdditionalFileDiagnosticAnalyzer : DiagnosticAnalyzer + { + public const string Id = "MockAdditionalDiagnostic"; + private readonly DiagnosticDescriptor _descriptor = new(Id, "MockAdditionalDiagnostic", "MockAdditionalDiagnostic", "InternalCategory", DiagnosticSeverity.Warning, isEnabledByDefault: true, helpLinkUri: "https://github.com/dotnet/roslyn"); + + public override ImmutableArray SupportedDiagnostics + => ImmutableArray.Create(_descriptor); + + public override void Initialize(AnalysisContext context) + => context.RegisterCompilationStartAction(CreateAnalyzerWithinCompilation); + + public void CreateAnalyzerWithinCompilation(CompilationStartAnalysisContext context) + => context.RegisterAdditionalFileAction(AnalyzeCompilation); + + public void AnalyzeCompilation(AdditionalFileAnalysisContext context) + => context.ReportDiagnostic(Diagnostic.Create(_descriptor, + location: Location.Create(context.AdditionalFile.Path, Text.TextSpan.FromBounds(0, 0), new Text.LinePositionSpan(new Text.LinePosition(0, 0), new Text.LinePosition(0, 0))), "args")); + } +} diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs index e4e8fc02075b6..4669c384bf3b3 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/PullDiagnosticTests.cs @@ -90,7 +90,8 @@ public async Task TestNoDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOf { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Default, useVSDiagnostics); + using var testLspServer = await CreateTestLspServerAsync(markup, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, DiagnosticMode.Default)); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -108,7 +109,8 @@ public async Task TestDocumentDiagnosticsForOpenFilesIfDefaultAndFeatureFlagOn(b { var markup = @"class A {"; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Default, useVSDiagnostics); + using var testLspServer = await CreateTestLspServerAsync(markup, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, DiagnosticMode.Default)); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -149,7 +151,7 @@ public async Task TestDocumentDiagnosticsForRemovedDocument(bool useVSDiagnostic results = await RunGetDocumentPullDiagnosticsAsync(testLspServer, document.GetURI(), useVSDiagnostics, results.Single().ResultId).ConfigureAwait(false); - Assert.Null(results.Single().Diagnostics); + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results.Single().Diagnostics); Assert.Null(results.Single().ResultId); } @@ -458,7 +460,8 @@ public async Task TestDocumentDiagnosticsFromRazorServer(bool useVSDiagnostics) @"class A {"; // Turn off pull diagnostics by default, but send a request to the razor LSP server which is always pull. - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Push, useVSDiagnostics, serverKind: WellKnownLspServerKinds.RazorLspServer); + using var testLspServer = await CreateTestLspServerAsync(markup, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, DiagnosticMode.Push, WellKnownLspServerKinds.RazorLspServer)); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -482,7 +485,8 @@ public async Task TestDocumentDiagnosticsFromLiveShareServer(bool useVSDiagnosti @"class A {"; // Turn off pull diagnostics by default, but send a request to the razor LSP server which is always pull. - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync(markup, BackgroundAnalysisScope.OpenFiles, DiagnosticMode.Push, useVSDiagnostics, serverKind: WellKnownLspServerKinds.LiveShareLspServer); + using var testLspServer = await CreateTestLspServerAsync(markup, + GetInitializationOptions(BackgroundAnalysisScope.OpenFiles, useVSDiagnostics, DiagnosticMode.Push, WellKnownLspServerKinds.LiveShareLspServer)); // Calling GetTextBuffer will effectively open the file. testLspServer.TestWorkspace.Documents.Single().GetTextBuffer(); @@ -658,11 +662,9 @@ public async Task TestWorkspaceDiagnosticsForSourceGeneratedFiles(bool useVSDiag var markup1 = @"class A {"; var markup2 = ""; - using var testLspServer = await CreateTestWorkspaceWithDiagnosticsAsync( + using var testLspServer = await CreateTestLspServerAsync( markups: Array.Empty(), - sourceGeneratedMarkups: new[] { markup1, markup2 }, - BackgroundAnalysisScope.FullSolution, - useVSDiagnostics); + GetInitializationOptions(BackgroundAnalysisScope.FullSolution, useVSDiagnostics, DiagnosticMode.Pull, sourceGeneratedMarkups: new[] { markup1, markup2 })); var results = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics); @@ -700,7 +702,7 @@ public async Task TestWorkspaceDiagnosticsForRemovedDocument(bool useVSDiagnosti // First doc should show up as removed. Assert.Equal(3, results2.Length); - Assert.Null(results2[0].Diagnostics); + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results2[0].Diagnostics); Assert.Null(results2[0].ResultId); // Second and third doc should be changed as the project has changed. diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs index c1c28061b6042..9a10419dedf31 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Diagnostics/WorkspaceProjectDiagnosticsTests.cs @@ -12,8 +12,8 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.SolutionCrawler; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.VisualStudio.LanguageServer.Protocol; using Xunit; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; namespace Microsoft.CodeAnalysis.LanguageServer.UnitTests.Diagnostics; public class WorkspaceProjectDiagnosticsTests : AbstractPullDiagnosticTestsBase @@ -55,17 +55,16 @@ public async Task TestWorkspaceDiagnosticsWithRemovedProject(bool useVSDiagnosti var results2 = await RunGetWorkspacePullDiagnosticsAsync(testLspServer, useVSDiagnostics, previousResults: CreateDiagnosticParamsFromPreviousReports(results)); Assert.Equal(2, results2.Length); - Assert.Null(results2[0].Diagnostics); + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results2[0].Diagnostics); Assert.Null(results2[0].ResultId); - Assert.Null(results2[1].Diagnostics); + Assert.Equal(useVSDiagnostics ? null : Array.Empty(), results2[1].Diagnostics); Assert.Null(results2[1].ResultId); } protected override TestComposition Composition => base.Composition.AddParts(typeof(MockProjectDiagnosticAnalyzer)); - protected override ImmutableDictionary> TestAnalyzers - => ImmutableDictionary.Create>() - .Add(LanguageNames.CSharp, ImmutableArray.Create(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp), new MockProjectDiagnosticAnalyzer())); + private protected override TestAnalyzerReferenceByLanguage TestAnalyzerReferences => new(ImmutableDictionary.Create>() + .Add(LanguageNames.CSharp, ImmutableArray.Create(DiagnosticExtensions.GetCompilerDiagnosticAnalyzer(LanguageNames.CSharp), new MockProjectDiagnosticAnalyzer()))); [DiagnosticAnalyzer(LanguageNames.CSharp), PartNotDiscoverable] private class MockProjectDiagnosticAnalyzer : DiagnosticAnalyzer diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs index c2fa4a2157de5..9127740b2806d 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Hover/HoverTests.cs @@ -179,7 +179,7 @@ static void Main(string[] args) "; - using var testLspServer = await CreateXmlTestLspServerAsync(workspaceXml, clientCapabilities: CapabilitiesWithVSExtensions); + using var testLspServer = await CreateXmlTestLspServerAsync(workspaceXml, initializationOptions: new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions }); var location = testLspServer.GetLocations("caret").Single(); foreach (var project in testLspServer.GetCurrentSolution().Projects) diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs index 751e7952e0c06..1ad0348068637 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Miscellaneous/LspMiscellaneousFilesWorkspaceTests.cs @@ -29,7 +29,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); // Open an empty loose file and make a request to verify it gets added to the misc workspace. @@ -52,7 +52,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); var looseFileUri = new Uri(@"C:\SomeFile.cs"); @@ -81,7 +81,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); // Open an empty loose file and make a request to verify it gets added to the misc workspace. @@ -106,7 +106,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(markup, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(markup, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); // Open a file that is part of a registered workspace and verify it is not present in the misc workspace. @@ -127,7 +127,7 @@ void M() }"; // Create a server that supports LSP misc files and verify no misc files present. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); Assert.Null(GetMiscellaneousDocument(testLspServer)); // Open an empty loose file and make a request to verify it gets added to the misc workspace. @@ -167,7 +167,7 @@ void M() }"; // Create a server that doesn't use the LSP misc files workspace. - using var testLspServer = await CreateTestLspServerAsync(string.Empty, new LSP.ClientCapabilities(), WellKnownLspServerKinds.AlwaysActiveVSLspServer); + using var testLspServer = await CreateTestLspServerAsync(string.Empty, new InitializationOptions { ServerKind = WellKnownLspServerKinds.AlwaysActiveVSLspServer }); Assert.Null(testLspServer.GetManagerAccessor().GetLspMiscellaneousFilesWorkspace()); // Open an empty loose file and make a request to verify it gets added to the misc workspace. diff --git a/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs index 8faf23f9e063b..7fe9c0354eb27 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/OnAutoInsert/OnAutoInsertTests.cs @@ -385,11 +385,11 @@ private async Task VerifyMarkupAndExpected( Task testLspServerTask; if (languageName == LanguageNames.CSharp) { - testLspServerTask = CreateTestLspServerAsync(markup, CapabilitiesWithVSExtensions, serverKind); + testLspServerTask = CreateTestLspServerAsync(markup, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, ServerKind = serverKind }); } else if (languageName == LanguageNames.VisualBasic) { - testLspServerTask = CreateVisualBasicTestLspServerAsync(markup, CapabilitiesWithVSExtensions, serverKind); + testLspServerTask = CreateVisualBasicTestLspServerAsync(markup, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions, ServerKind = serverKind }); } else { diff --git a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs index 88d9d347be81c..71cf34fe4ba58 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/References/FindAllReferencesHandlerTests.cs @@ -171,7 +171,7 @@ void M2() }" }; - using var testLspServer = await CreateTestLspServerAsync(markups, CapabilitiesWithVSExtensions); + using var testLspServer = await CreateTestLspServerAsync(markups, new InitializationOptions { ClientCapabilities = CapabilitiesWithVSExtensions }); var results = await RunFindAllReferencesAsync(testLspServer, testLspServer.GetLocations("caret").First()); AssertLocationsEqual(testLspServer.GetLocations("reference"), results.Select(result => result.Location)); diff --git a/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs b/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs index 6443727f79469..0c376640ebc85 100644 --- a/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs +++ b/src/Features/LanguageServer/ProtocolUnitTests/Workspaces/LspWorkspaceManagerTests.cs @@ -191,7 +191,7 @@ public async Task TestLspTransfersDocumentToNewWorkspaceAsync() var markup = "One"; // Create a server that includes the LSP misc files workspace so we can test transfers to and from it. - using var testLspServer = await CreateTestLspServerAsync(markup, new ClientCapabilities(), serverKind: WellKnownLspServerKinds.CSharpVisualBasicLspServer); + using var testLspServer = await CreateTestLspServerAsync(markup, new InitializationOptions { ServerKind = WellKnownLspServerKinds.CSharpVisualBasicLspServer }); // Create a new document, but do not update the workspace solution yet. var newDocumentId = DocumentId.CreateNewId(testLspServer.TestWorkspace.CurrentSolution.ProjectIds[0]); @@ -433,8 +433,8 @@ public async Task TestSeparateWorkspaceManagerPerServerAsync() var documentUri = testWorkspace.CurrentSolution.Projects.First().Documents.First().GetURI(); - using var testLspServerOne = await TestLspServer.CreateAsync(testWorkspace, clientCapabilities: new(), WellKnownLspServerKinds.AlwaysActiveVSLspServer); - using var testLspServerTwo = await TestLspServer.CreateAsync(testWorkspace, clientCapabilities: new(), WellKnownLspServerKinds.AlwaysActiveVSLspServer); + using var testLspServerOne = await TestLspServer.CreateAsync(testWorkspace, new InitializationOptions()); + using var testLspServerTwo = await TestLspServer.CreateAsync(testWorkspace, new InitializationOptions()); Assert.NotEqual(testLspServerOne.GetManager(), testLspServerTwo.GetManager()); From d1fd2033678069c4cfcc4ce17bd3367a896d60a2 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Thu, 16 Jun 2022 15:33:35 -0700 Subject: [PATCH 41/42] Improve error message for lookup on a type parameter. (#61967) Closes #61486. --- .../Portable/Binder/Binder_Expressions.cs | 2 +- .../CSharp/Portable/Binder/Binder_Query.cs | 2 +- .../CSharp/Portable/CSharpResources.resx | 2 +- .../Portable/xlf/CSharpResources.cs.xlf | 4 +- .../Portable/xlf/CSharpResources.de.xlf | 4 +- .../Portable/xlf/CSharpResources.es.xlf | 4 +- .../Portable/xlf/CSharpResources.fr.xlf | 4 +- .../Portable/xlf/CSharpResources.it.xlf | 4 +- .../Portable/xlf/CSharpResources.ja.xlf | 4 +- .../Portable/xlf/CSharpResources.ko.xlf | 4 +- .../Portable/xlf/CSharpResources.pl.xlf | 4 +- .../Portable/xlf/CSharpResources.pt-BR.xlf | 4 +- .../Portable/xlf/CSharpResources.ru.xlf | 4 +- .../Portable/xlf/CSharpResources.tr.xlf | 4 +- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 4 +- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 4 +- .../Semantic/Semantics/LocalFunctionTests.cs | 20 +-- .../Semantic/Semantics/SemanticErrorTests.cs | 28 ++-- .../Test/Symbol/Symbols/IndexerTests.cs | 8 +- .../StaticAbstractMembersInInterfacesTests.cs | 153 +++++++++--------- 20 files changed, 133 insertions(+), 134 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 1bb2bb6ac85df..3dd4a25517255 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -6525,7 +6525,7 @@ BoundExpression tryBindMemberAccessWithBoundTypeLeft( } else if (lookupResult.IsClear) { - Error(diagnostics, ErrorCode.ERR_BadSKunknown, boundLeft.Syntax, leftType, MessageID.IDS_SK_TYVAR.Localize()); + Error(diagnostics, ErrorCode.ERR_LookupInTypeVariable, boundLeft.Syntax, leftType); return BadExpression(node, LookupResultKind.NotAValue, boundLeft); } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs index b09594440f0d6..7e3b0808b4642 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Query.cs @@ -781,7 +781,7 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r { if (ultimateReceiver.Type.TypeKind == TypeKind.TypeParameter) { - // https://github.com/dotnet/roslyn/issues/53796: Do we really want to enable usage of static abstract members here? + // We don't want to enable usage of static abstract members here Error(diagnostics, ErrorCode.ERR_BadSKunknown, ultimateReceiver.Syntax, ultimateReceiver.Type, MessageID.IDS_SK_TYVAR.Localize()); } } diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index eb7e597e799d8..c530d2b4881df 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -2058,7 +2058,7 @@ If such a class is used as a base class and if the deriving class defines a dest Inconsistent accessibility: constraint type '{1}' is less accessible than '{0}' - Cannot do member lookup in '{0}' because it is a type parameter + Cannot do non-virtual member lookup in '{0}' because it is a type parameter Invalid constraint type. A type used as a constraint must be an interface, a non-sealed class or a type parameter. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 247b9cb38b8af..87246171eee3c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -6501,8 +6501,8 @@ Pokud se taková třída používá jako základní třída a pokud odvozující - Cannot do member lookup in '{0}' because it is a type parameter - Nejde vyhledávat člena v {0}, protože se jedná o parametr typu. + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + Nejde vyhledávat člena v {0}, protože se jedná o parametr typu. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 950fe236f2924..b61b5ed167aae 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -6501,8 +6501,8 @@ Wenn solch eine Klasse als Basisklasse verwendet wird und die ableitende Klasse - Cannot do member lookup in '{0}' because it is a type parameter - In "{0}" kann kein Memberlookup ausgeführt werden, da es sich um einen Typparameter handelt. + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + In "{0}" kann kein Memberlookup ausgeführt werden, da es sich um einen Typparameter handelt. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index bac92ce891cac..42f9f35cb91b9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -6501,8 +6501,8 @@ Si se utiliza una clase de este tipo como clase base y si la clase derivada defi - Cannot do member lookup in '{0}' because it is a type parameter - No se pueden buscar miembros en '{0}' porque es un parámetro de tipo + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + No se pueden buscar miembros en '{0}' porque es un parámetro de tipo diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 5e9c49f4febd5..13241b4223f5e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -6501,8 +6501,8 @@ Si une telle classe est utilisée en tant que classe de base et si la classe dé - Cannot do member lookup in '{0}' because it is a type parameter - Impossible de rechercher un membre dans '{0}', car il s'agit d'un paramètre de type + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + Impossible de rechercher un membre dans '{0}', car il s'agit d'un paramètre de type diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 1638baa973b06..84bdb0023a364 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -6501,8 +6501,8 @@ Se si usa tale classe come classe base e se la classe di derivazione definisce u - Cannot do member lookup in '{0}' because it is a type parameter - Non è possibile eseguire la ricerca di membri in '{0}' perché è un parametro di tipo + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + Non è possibile eseguire la ricerca di membri in '{0}' perché è un parametro di tipo diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index d6a00b35553f5..ab77fe45a7ad6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -6501,8 +6501,8 @@ If such a class is used as a base class and if the deriving class defines a dest - Cannot do member lookup in '{0}' because it is a type parameter - 型パラメーターであるため、'{0}' でメンバーの照合を行えません + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + 型パラメーターであるため、'{0}' でメンバーの照合を行えません diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 208d03ab9107c..200000e97e06f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -6500,8 +6500,8 @@ If such a class is used as a base class and if the deriving class defines a dest - Cannot do member lookup in '{0}' because it is a type parameter - '{0}'은(는) 형식 매개 변수이므로 멤버를 조회할 수 없습니다. + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + '{0}'은(는) 형식 매개 변수이므로 멤버를 조회할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index c67e7c00590de..094ec9c91c813 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -6501,8 +6501,8 @@ Jeśli taka klasa zostanie użyta jako klasa bazowa i klasa pochodna definiuje d - Cannot do member lookup in '{0}' because it is a type parameter - Nie można wyszukać składowej w elemencie „{0}”, ponieważ to jest parametr typu + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + Nie można wyszukać składowej w elemencie „{0}”, ponieważ to jest parametr typu diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 8c300c13b5455..671f416e8998f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -6501,8 +6501,8 @@ Se tal classe for usada como uma classe base e se a classe derivada definir um d - Cannot do member lookup in '{0}' because it is a type parameter - Não é possível fazer pesquisa de membro em "{0}" porque ele é um parâmetro de tipo + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + Não é possível fazer pesquisa de membro em "{0}" porque ele é um parâmetro de tipo diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index e064c1b3c9425..984d8dada352f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -6501,8 +6501,8 @@ If such a class is used as a base class and if the deriving class defines a dest - Cannot do member lookup in '{0}' because it is a type parameter - Не удается выполнить поиск члена в "{0}", так как это параметр типа. + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + Не удается выполнить поиск члена в "{0}", так как это параметр типа. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 210b68d511bc7..08d867c8ba79b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -6501,8 +6501,8 @@ Bu sınıf temel sınıf olarak kullanılırsa ve türetilen sınıf bir yıkıc - Cannot do member lookup in '{0}' because it is a type parameter - '{0}' bir tür parametresi olduğundan burada üye araması yapılamıyor + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + '{0}' bir tür parametresi olduğundan burada üye araması yapılamıyor diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 5fa9e3e96bcaa..9fbd0cd1d0a89 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -6506,8 +6506,8 @@ If such a class is used as a base class and if the deriving class defines a dest - Cannot do member lookup in '{0}' because it is a type parameter - “{0}”是一个类型参数,无法在其中执行成员查找 + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + “{0}”是一个类型参数,无法在其中执行成员查找 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 3b863601601c8..a66236e5c563d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -6501,8 +6501,8 @@ If such a class is used as a base class and if the deriving class defines a dest - Cannot do member lookup in '{0}' because it is a type parameter - 無法在 '{0}' 中進行成員查詢,因為其為類型參數 + Cannot do non-virtual member lookup in '{0}' because it is a type parameter + 無法在 '{0}' 中進行成員查詢,因為其為類型參數 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs index 9a6a8774cdc96..df87ce161d64c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs @@ -7623,12 +7623,12 @@ public MyAttribute(string name1) { } comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); comp.VerifyDiagnostics( - // (13,20): error CS0119: 'TParameter' is a type parameter, which is not valid in the given context + // (13,20): error CS0704: Cannot do non-virtual member lookup in 'TParameter' because it is a type parameter // [My(nameof(TParameter.Constant))] // 1 - Diagnostic(ErrorCode.ERR_BadSKunknown, "TParameter").WithArguments("TParameter", "type parameter").WithLocation(13, 20), - // (17,16): error CS0119: 'TParameter' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "TParameter").WithArguments("TParameter").WithLocation(13, 20), + // (17,16): error CS0704: Cannot do non-virtual member lookup in 'TParameter' because it is a type parameter // [My(nameof(TParameter.Constant))] // 2 - Diagnostic(ErrorCode.ERR_BadSKunknown, "TParameter").WithArguments("TParameter", "type parameter").WithLocation(17, 16) + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "TParameter").WithArguments("TParameter").WithLocation(17, 16) ); VerifyTParameter(comp, 0, "void local()"); @@ -8473,9 +8473,9 @@ public MyAttribute(string name) { } ", targetFramework: TargetFramework.NetCoreApp); comp.VerifyDiagnostics( - // (2,5): error CS0119: 'TParameter' is a type parameter, which is not valid in the given context + // (2,5): error CS0704: Cannot do non-virtual member lookup in 'TParameter' because it is a type parameter // [My(TParameter.Constant)] - Diagnostic(ErrorCode.ERR_BadSKunknown, "TParameter").WithArguments("TParameter", "type parameter").WithLocation(2, 5) + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "TParameter").WithArguments("TParameter").WithLocation(2, 5) ); VerifyTParameter(comp, 0, "C"); @@ -8563,9 +8563,9 @@ public MyAttribute(string name1) { } "; var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp); comp.VerifyDiagnostics( - // (2,12): error CS0119: 'TParameter' is a type parameter, which is not valid in the given context + // (2,12): error CS0704: Cannot do non-virtual member lookup in 'TParameter' because it is a type parameter // [My(nameof(TParameter.Constant))] - Diagnostic(ErrorCode.ERR_BadSKunknown, "TParameter").WithArguments("TParameter", "type parameter").WithLocation(2, 12) + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "TParameter").WithArguments("TParameter").WithLocation(2, 12) ); VerifyTParameter(comp, 0, "R"); } @@ -8643,9 +8643,9 @@ public interface I "; var comp = CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp); comp.VerifyDiagnostics( - // (2,5): error CS0119: 'TParameter' is a type parameter, which is not valid in the given context + // (2,5): error CS0704: Cannot do non-virtual member lookup in 'TParameter' because it is a type parameter // [My(TParameter.Constant)] - Diagnostic(ErrorCode.ERR_BadSKunknown, "TParameter").WithArguments("TParameter", "type parameter").WithLocation(2, 5) + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "TParameter").WithArguments("TParameter").WithLocation(2, 5) ); VerifyTParameter(comp, 0, "R"); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs index 44a2f401879ed..095dadc92ae66 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/SemanticErrorTests.cs @@ -2564,15 +2564,15 @@ static void M(T t) } }"; CreateCompilation(source).VerifyDiagnostics( - // (10,27): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (10,9): error CS0704: Cannot do non-virtual member lookup in 'U' because it is a type parameter // U.ReferenceEquals(T.F, null); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter"), - // (10,9): error CS0119: 'U' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "U").WithArguments("U").WithLocation(10, 9), + // (10,27): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // U.ReferenceEquals(T.F, null); - Diagnostic(ErrorCode.ERR_BadSKunknown, "U").WithArguments("U", "type parameter"), - // (11,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(10, 27), + // (11,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.M(); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter"), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(11, 9), // (3,28): warning CS0649: Field 'A.F' is never assigned to, and will always have its default value null // internal static object F; Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F").WithArguments("A.F", "null") @@ -11747,30 +11747,30 @@ static object M() } }"; CreateCompilation(text).VerifyDiagnostics( - // (9,15): error CS0704: Cannot do member lookup in 'T' because it is a type parameter class E : T.B { } + // (9,15): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter class E : T.B { } Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T.B").WithArguments("T"), - // (10,30): error CS0704: Cannot do member lookup in 'T' because it is a type parameter + // (10,30): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // interface I where U : T.B { } Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T.B").WithArguments("T"), - // (11,6): error CS0704: Cannot do member lookup in 'T' because it is a type parameter + // (11,6): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // [T.B] Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T.B").WithArguments("T"), - // (14,9): error CS0704: Cannot do member lookup in 'T' because it is a type parameter + // (14,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.C b1 = new T.C(); Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T.C").WithArguments("T"), - // (14,30): error CS0704: Cannot do member lookup in 'T' because it is a type parameter + // (14,30): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.C b1 = new T.C(); Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T.C").WithArguments("T"), // (15,9): error CS0307: The type parameter 'T' cannot be used with type arguments // T.B b2 = null; Diagnostic(ErrorCode.ERR_TypeArgsNotAllowed, "T").WithArguments("T", "type parameter"), - // (16,22): error CS0704: Cannot do member lookup in 'T' because it is a type parameter + // (16,22): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // b1 = default(T.B); Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T.B").WithArguments("T"), - // (17,27): error CS0704: Cannot do member lookup in 'T' because it is a type parameter + // (17,27): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // object o = typeof(T.C); Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T.C").WithArguments("T"), - // (18,18): error CS0704: Cannot do member lookup in 'T' because it is a type parameter + // (18,18): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // o = o as T.B; Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T.B").WithArguments("T") ); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/IndexerTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/IndexerTests.cs index cf9f689556b1f..77fa8d2570828 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/IndexerTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/IndexerTests.cs @@ -1955,12 +1955,12 @@ class B where T : Q // (7,25): error CS0110: The evaluation of the constant value for 'P.Constant2' involves a circular definition // public const string Constant2 = Q.Constant2; Diagnostic(ErrorCode.ERR_CircConstValue, "Constant2").WithArguments("P.Constant2"), - // (18,18): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (18,18): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // [IndexerName(T.Constant1)] - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter"), - // (24,18): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(18, 18), + // (24,18): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // [IndexerName(T.Constant2)] - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter")); + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(24, 18)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs index 20d4478936bb2..1ddd2cb97faed 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/StaticAbstractMembersInInterfacesTests.cs @@ -8101,15 +8101,15 @@ static void MT2() where T : I1 // (30,9): error CS0176: Member 'I1.M04()' cannot be accessed with an instance reference; qualify it with a type name instead // x.M04(); Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.M04").WithArguments("I1.M04()").WithLocation(30, 9), - // (35,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.M03(); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 9), - // (36,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 9), + // (36,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.M04(); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 9), - // (37,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 9), + // (37,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.M00(); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 9), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 9), // (38,11): error CS0122: 'I1.M05()' is inaccessible due to its protection level // T.M05(); Diagnostic(ErrorCode.ERR_BadAccess, "M05").WithArguments("I1.M05()").WithLocation(38, 11), @@ -8172,15 +8172,15 @@ static void MT2() where T : I1 targetFramework: _supportingFramework); compilation1.VerifyDiagnostics( - // (35,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.M03); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 20), - // (36,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 20), + // (36,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.M04); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 20), - // (37,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 20), + // (37,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.M00); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 20), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 20), // (38,22): error CS0122: 'I1.M05()' is inaccessible due to its protection level // _ = nameof(T.M05); Diagnostic(ErrorCode.ERR_BadAccess, "M05").WithArguments("I1.M05()").WithLocation(38, 22) @@ -8386,7 +8386,6 @@ static void M02() where T : I1 parseOptions: TestOptions.RegularPreview, targetFramework: _supportingFramework); - // https://github.com/dotnet/roslyn/issues/53796: Confirm whether we want to enable the 'from t in T' scenario. compilation1.VerifyDiagnostics( // (11,23): error CS0119: 'T' is a type parameter, which is not valid in the given context // _ = from t in T select t + 1; @@ -12746,15 +12745,15 @@ static void MT2() where T : I1 // (30,13): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead // _ = x.P04; Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 13), - // (35,13): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,13): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = T.P03; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 13), - // (36,13): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 13), + // (36,13): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = T.P04; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 13), - // (37,13): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 13), + // (37,13): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = T.P00; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 13), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 13), // (38,15): error CS0122: 'I1.P05' is inaccessible due to its protection level // _ = T.P05; Diagnostic(ErrorCode.ERR_BadAccess, "P05").WithArguments("I1.P05").WithLocation(38, 15), @@ -12837,15 +12836,15 @@ static void MT2() where T : I1 // (30,9): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead // x.P04 = 1; Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 9), - // (35,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P03 = 1; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 9), - // (36,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 9), + // (36,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P04 = 1; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 9), - // (37,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 9), + // (37,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P00 = 1; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 9), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 9), // (38,11): error CS0122: 'I1.P05' is inaccessible due to its protection level // T.P05 = 1; Diagnostic(ErrorCode.ERR_BadAccess, "P05").WithArguments("I1.P05").WithLocation(38, 11), @@ -12937,15 +12936,15 @@ static void MT2() where T : I1 // (30,9): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead // x.P04 += 1; Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 9), - // (35,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P03 += 1; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 9), - // (36,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 9), + // (36,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P04 += 1; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 9), - // (37,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 9), + // (37,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P00 += 1; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 9), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 9), // (38,11): error CS0122: 'I1.P05' is inaccessible due to its protection level // T.P05 += 1; Diagnostic(ErrorCode.ERR_BadAccess, "P05").WithArguments("I1.P05").WithLocation(38, 11), @@ -13023,15 +13022,15 @@ static void MT2() where T : I1 // (30,20): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead // _ = nameof(x.P04); Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 20), - // (35,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.P03); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 20), - // (36,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 20), + // (36,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.P04); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 20), - // (37,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 20), + // (37,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.P00); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 20), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 20), // (38,22): error CS0122: 'I1.P05' is inaccessible due to its protection level // _ = nameof(T.P05); Diagnostic(ErrorCode.ERR_BadAccess, "P05").WithArguments("I1.P05").WithLocation(38, 22) @@ -13734,15 +13733,15 @@ static void MT2() where T : I1 // (30,9): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead // x.P04 += null; Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 9), - // (35,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P03 += null; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 9), - // (36,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 9), + // (36,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P04 += null; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 9), - // (37,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 9), + // (37,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P00 += null; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 9), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 9), // (38,11): error CS0122: 'I1.P05' is inaccessible due to its protection level // T.P05 += null; Diagnostic(ErrorCode.ERR_BadAccess, "P05").WithArguments("I1.P05").WithLocation(38, 11), @@ -13831,15 +13830,15 @@ static void MT2() where T : I1 // (30,9): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead // x.P04 -= null; Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 9), - // (35,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P03 -= null; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 9), - // (36,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 9), + // (36,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P04 -= null; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 9), - // (37,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 9), + // (37,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.P00 -= null; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 9), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 9), // (38,11): error CS0122: 'I1.P05' is inaccessible due to its protection level // T.P05 -= null; Diagnostic(ErrorCode.ERR_BadAccess, "P05").WithArguments("I1.P05").WithLocation(38, 11), @@ -13914,15 +13913,15 @@ static void MT2() where T : I1 // (30,20): error CS0176: Member 'I1.P04' cannot be accessed with an instance reference; qualify it with a type name instead // _ = nameof(x.P04); Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.P04").WithArguments("I1.P04").WithLocation(30, 20), - // (35,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.P03); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 20), - // (36,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 20), + // (36,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.P04); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 20), - // (37,20): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 20), + // (37,20): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = nameof(T.P00); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 20), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 20), // (38,22): error CS0122: 'I1.P05' is inaccessible due to its protection level // _ = nameof(T.P05); Diagnostic(ErrorCode.ERR_BadAccess, "P05").WithArguments("I1.P05").WithLocation(38, 22) @@ -14339,12 +14338,12 @@ static string M03() where T : I1 targetFramework: _supportingFramework); compilation1.VerifyDiagnostics( - // (6,9): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (6,9): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // T.Item[0] += 1; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(6, 9), - // (11,23): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(6, 9), + // (11,23): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // return nameof(T.Item); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(11, 23) + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(11, 23) ); var source2 = @@ -14551,15 +14550,15 @@ static void MT2() where T : I1 // (30,28): error CS0176: Member 'I1.M04()' cannot be accessed with an instance reference; qualify it with a type name instead // _ = (System.Action)x.M04; Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.M04").WithArguments("I1.M04()").WithLocation(30, 28), - // (35,28): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,28): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = (System.Action)T.M03; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 28), - // (36,28): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 28), + // (36,28): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = (System.Action)T.M04; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 28), - // (37,28): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 28), + // (37,28): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = (System.Action)T.M00; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 28), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 28), // (38,30): error CS0122: 'I1.M05()' is inaccessible due to its protection level // _ = (System.Action)T.M05; Diagnostic(ErrorCode.ERR_BadAccess, "M05").WithArguments("I1.M05()").WithLocation(38, 30), @@ -14979,15 +14978,15 @@ static void MT2() where T : I1 // (30,31): error CS0176: Member 'I1.M04()' cannot be accessed with an instance reference; qualify it with a type name instead // _ = new System.Action(x.M04); Diagnostic(ErrorCode.ERR_ObjectProhibited, "x.M04").WithArguments("I1.M04()").WithLocation(30, 31), - // (35,31): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,31): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = new System.Action(T.M03); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 31), - // (36,31): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 31), + // (36,31): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = new System.Action(T.M04); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 31), - // (37,31): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 31), + // (37,31): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = new System.Action(T.M00); - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 31), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 31), // (38,33): error CS0122: 'I1.M05()' is inaccessible due to its protection level // _ = new System.Action(T.M05); Diagnostic(ErrorCode.ERR_BadAccess, "M05").WithArguments("I1.M05()").WithLocation(38, 33), @@ -15251,15 +15250,15 @@ static void MT2() where T : I1 // (30,13): error CS8757: No overload for 'M04' matches function pointer 'delegate*' // _ = (delegate*)&x.M04; Diagnostic(ErrorCode.ERR_MethFuncPtrMismatch, "(delegate*)&x.M04").WithArguments("M04", "delegate*").WithLocation(30, 13), - // (35,31): error CS0119: 'T' is a type parameter, which is not valid in the given context + // (35,31): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = (delegate*)&T.M03; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(35, 31), - // (36,31): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(35, 31), + // (36,31): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = (delegate*)&T.M04; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(36, 31), - // (37,31): error CS0119: 'T' is a type parameter, which is not valid in the given context + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(36, 31), + // (37,31): error CS0704: Cannot do non-virtual member lookup in 'T' because it is a type parameter // _ = (delegate*)&T.M00; - Diagnostic(ErrorCode.ERR_BadSKunknown, "T").WithArguments("T", "type parameter").WithLocation(37, 31), + Diagnostic(ErrorCode.ERR_LookupInTypeVariable, "T").WithArguments("T").WithLocation(37, 31), // (38,33): error CS0122: 'I1.M05()' is inaccessible due to its protection level // _ = (delegate*)&T.M05; Diagnostic(ErrorCode.ERR_BadAccess, "M05").WithArguments("I1.M05()").WithLocation(38, 33), From 2f9faa037fb7ff875ece0e944de40a4ae3d255d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Matou=C5=A1ek?= Date: Thu, 16 Jun 2022 16:47:04 -0700 Subject: [PATCH 42/42] SerializableOptionSet and IOptionService cleanup (#61839) * Remove serialization * Workspace tests refactoring, add public option tests * Remove usage of Workspace.SetOptions from tests * Fix * Remove Workspace.SetOptions * Remove serializable options from SerializableOptionSet * Cleanup * Merge WorkspaceOptionSet into SerializableOptionSet * Rename * IOptionService * Remove unused obsolete Razor EA API * Remove * Formatting * Feedback --- .../OverrideCompletionProviderTests.cs | 13 +- ...eCompletionProviderTests_ExpressionBody.cs | 15 +- .../ConvertNamespaceCommandHandlerTests.cs | 4 +- ...ts.cs => WorkspaceTests_EditorFeatures.cs} | 51 +- .../LegacyGlobalOptionsWorkspaceService.cs | 3 - ...AbstractSplitCommentCommandHandlerTests.cs | 4 +- .../ProjectCacheHostServiceFactoryTests.cs | 27 +- .../AbstractAutomaticLineEnderTests.cs | 3 +- .../AbstractArgumentProviderTests`1.cs | 11 +- .../AbstractCompletionProviderTests.cs | 13 +- .../Extensions/SolutionExtensions.cs | 22 - .../ExtractInterfaceTestState.cs | 6 +- .../TestOptionsServiceFactory.cs | 41 -- ...ceWithSharedGlobalOptionsServiceFactory.cs | 43 -- .../EndConstructCommandHandlerTests.vb | 2 +- .../Diagnostics/AnalyzerConfigOptionSet.cs | 2 +- ...RazorCSharpFormattingInteractionService.cs | 38 -- .../Razor/RazorGlobalOptions.cs | 3 +- .../Mocks/OptionServiceMock.cs | 102 ---- .../Core/Impl/Options/AbstractOptionPage.cs | 16 +- .../Options/AbstractOptionPreviewViewModel.cs | 2 +- .../Remote/SnapshotSerializationTests.cs | 89 --- .../Services/ServiceHubServicesTests.cs | 10 - .../InProcess/VisualStudioWorkspace_InProc.cs | 78 +-- .../VisualStudioWorkspace_OutOfProc.cs | 9 - ...soft.CodeAnalysis.CSharp.Workspaces.csproj | 1 + .../Portable/CodeStyle/CodeStyleOption.cs | 12 - .../Core/Portable/Formatting/Formatter.cs | 6 +- .../Portable/Options/DocumentOptionSet.cs | 2 +- .../Portable/Options/GlobalOptionService.cs | 71 +-- .../Portable/Options/IGlobalOptionService.cs | 9 +- .../ILegacyGlobalOptionsWorkspaceService.cs | 1 - .../Options/ILegacyWorkspaceOptionService.cs | 28 + .../Core/Portable/Options/IOptionService.cs | 88 --- .../Portable/Options/OptionServiceFactory.cs | 70 +-- .../OptionSet+AnalyzerConfigOptionsImpl.cs | 6 +- .../Core/Portable/Options/OptionSet.cs | 6 +- ...erializableOptionSet.WorkspaceOptionSet.cs | 82 --- .../Portable/Options/SerializableOptionSet.cs | 524 ------------------ .../Portable/Options/SolutionOptionSet.cs | 108 ++++ .../Portable/Simplification/Simplifier.cs | 2 +- .../Portable/Workspace/Solution/Solution.cs | 6 +- .../Workspace/Solution/SolutionInfo.cs | 14 - .../Workspace/Solution/SolutionState.cs | 87 +-- .../Core/Portable/Workspace/Workspace.cs | 36 +- .../CoreTest/Formatter/FormatterTests.cs | 79 +-- .../WorkspaceServices/TestOptionService.cs | 71 --- .../TestOptionsServiceFactory.cs | 41 -- .../CoreTest/Options/OptionsTestHelpers.cs | 113 ++++ .../CoreTest/SolutionTests/SolutionTests.cs | 7 +- .../CoreTest/TestCompositionTests.cs | 4 +- ...ceTests.cs => GlobalOptionServiceTests.cs} | 159 ++++-- .../WorkspaceTests/WorkspaceReferenceTests.cs | 2 +- ...ralWorkspaceTests.cs => WorkspaceTests.cs} | 39 +- .../CoreTestUtilities/OptionsCollection.cs | 2 +- .../Remote/ServiceHub/Host/RemoteWorkspace.cs | 8 +- .../Core/CodeStyle/CodeStyleOption2`1.cs | 53 +- .../Serialization/NamingStylePreferences.cs | 36 +- 58 files changed, 594 insertions(+), 1786 deletions(-) rename src/EditorFeatures/CSharpTest/Workspaces/{WorkspaceTests.cs => WorkspaceTests_EditorFeatures.cs} (97%) delete mode 100644 src/EditorFeatures/TestUtilities/Extensions/SolutionExtensions.cs delete mode 100644 src/EditorFeatures/TestUtilities/TestOptionsServiceFactory.cs delete mode 100644 src/EditorFeatures/TestUtilities/TestOptionsServiceWithSharedGlobalOptionsServiceFactory.cs delete mode 100644 src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs create mode 100644 src/Workspaces/Core/Portable/Options/ILegacyWorkspaceOptionService.cs delete mode 100644 src/Workspaces/Core/Portable/Options/IOptionService.cs delete mode 100644 src/Workspaces/Core/Portable/Options/SerializableOptionSet.WorkspaceOptionSet.cs delete mode 100644 src/Workspaces/Core/Portable/Options/SerializableOptionSet.cs create mode 100644 src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs delete mode 100644 src/Workspaces/CoreTest/Host/WorkspaceServices/TestOptionService.cs delete mode 100644 src/Workspaces/CoreTest/Host/WorkspaceServices/TestOptionsServiceFactory.cs create mode 100644 src/Workspaces/CoreTest/Options/OptionsTestHelpers.cs rename src/Workspaces/CoreTest/WorkspaceServiceTests/{OptionServiceTests.cs => GlobalOptionServiceTests.cs} (71%) rename src/Workspaces/CoreTest/WorkspaceTests/{GeneralWorkspaceTests.cs => WorkspaceTests.cs} (85%) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs index 2c5d506de4898..9e35221582e02 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -29,12 +30,12 @@ public class OverrideCompletionProviderTests : AbstractCSharpCompletionProviderT internal override Type GetCompletionProviderType() => typeof(OverrideCompletionProvider); - protected override OptionSet WithChangedNonCompletionOptions(OptionSet options) - { - return base.WithChangedNonCompletionOptions(options) - .WithChangedOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithSilentEnforcement) - .WithChangedOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.NeverWithSilentEnforcement); - } + internal override OptionsCollection NonCompletionOptions + => new(LanguageNames.CSharp) + { + { CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.NeverWithSilentEnforcement }, + { CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.NeverWithSilentEnforcement } + }; #region "CompletionItem tests" diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests_ExpressionBody.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests_ExpressionBody.cs index affa3a39d2530..2ea9deb38ff82 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests_ExpressionBody.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests_ExpressionBody.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; @@ -22,13 +23,13 @@ public class OverrideCompletionProviderTests_ExpressionBody : AbstractCSharpComp internal override Type GetCompletionProviderType() => typeof(OverrideCompletionProvider); - protected override OptionSet WithChangedNonCompletionOptions(OptionSet options) - { - return base.WithChangedNonCompletionOptions(options) - .WithChangedOption(CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement) - .WithChangedOption(CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement) - .WithChangedOption(CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement); - } + internal override OptionsCollection NonCompletionOptions + => new(LanguageNames.CSharp) + { + { CSharpCodeStyleOptions.PreferExpressionBodiedAccessors, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement }, + { CSharpCodeStyleOptions.PreferExpressionBodiedProperties, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement }, + { CSharpCodeStyleOptions.PreferExpressionBodiedMethods, CSharpCodeStyleOptions.WhenPossibleWithSuggestionEnforcement } + }; [WorkItem(16331, "https://github.com/dotnet/roslyn/issues/16334")] [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] diff --git a/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs b/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs index c9a278fbd8f5e..f475a3f686e5d 100644 --- a/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs +++ b/src/EditorFeatures/CSharpTest/ConvertNamespace/ConvertNamespaceCommandHandlerTests.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.Editor.CSharp.CompleteStatement; using Microsoft.CodeAnalysis.Editor.Shared.Options; using Microsoft.CodeAnalysis.Editor.UnitTests; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.VisualStudio.Commanding; using Roslyn.Test.Utilities; @@ -84,8 +85,7 @@ class C } }"); - testState.Workspace.SetOptions(testState.Workspace.Options.WithChangedOption( - FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon, false)); + testState.Workspace.GlobalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.AutomaticallyCompleteStatementOnSemicolon), false); testState.SendTypeChar(';'); testState.AssertCodeIs( diff --git a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs b/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs similarity index 97% rename from src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.cs rename to src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests_EditorFeatures.cs index 9fec98f650726..1c702ce23bc64 100644 --- a/src/EditorFeatures/CSharpTest/Workspaces/WorkspaceTests.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.Indentation; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; @@ -30,7 +31,7 @@ namespace Microsoft.CodeAnalysis.UnitTests.Workspaces { [UseExportProvider] - public partial class WorkspaceTests : TestBase + public class WorkspaceTests_EditorFeatures : TestBase { private static TestWorkspace CreateWorkspace( string workspaceKind = null, @@ -1387,34 +1388,33 @@ public void TestVersionStamp_Default() [Fact, WorkItem(19284, "https://github.com/dotnet/roslyn/issues/19284")] public void TestSolutionWithOptions() { - using var workspace = CreateWorkspace(); - - var document = new TestHostDocument("class C { }"); - - var project1 = new TestHostProject(workspace, document, name: "project1"); - - workspace.AddTestProject(project1); + using var workspace1 = CreateWorkspace(); + var solution = workspace1.CurrentSolution; - var solution = workspace.CurrentSolution; var optionKey = new OptionKey2(FormattingOptions2.SmartIndent, LanguageNames.CSharp); - var optionValue = solution.Options.GetOption(optionKey); - Assert.Equal(FormattingOptions2.IndentStyle.Smart, optionValue); + var defaultValue = solution.Options.GetOption(optionKey); + var changedValue = FormattingOptions.IndentStyle.Block; + Assert.NotEqual(defaultValue, changedValue); - var newOptions = solution.Options.WithChangedOption(optionKey, FormattingOptions2.IndentStyle.Block); + var newOptions = solution.Options.WithChangedOption(optionKey, changedValue); var newSolution = solution.WithOptions(newOptions); var newOptionValue = newSolution.Options.GetOption(optionKey); - Assert.Equal(FormattingOptions2.IndentStyle.Block, newOptionValue); + Assert.Equal(changedValue, newOptionValue); + + Assert.True(workspace1.TryApplyChanges(newSolution)); - var applied = workspace.TryApplyChanges(newSolution); - Assert.True(applied); + var currentOptionValue = workspace1.CurrentSolution.Options.GetOption(optionKey); + Assert.Equal(changedValue, currentOptionValue); - var currentOptionValue = workspace.CurrentSolution.Options.GetOption(optionKey); - Assert.Equal(FormattingOptions2.IndentStyle.Block, currentOptionValue); + // option is set to global options that are shared among all workspaces + using var workspace2 = CreateWorkspace(); + var value2 = workspace2.Options.GetOption(optionKey); + Assert.Equal(changedValue, value2); } - [CombinatorialData] - [Theory, WorkItem(19284, "https://github.com/dotnet/roslyn/issues/19284")] - public void TestOptionChangedHandlerInvokedAfterCurrentSolutionChanged(bool testDeprecatedOptionsSetter) + [Obsolete] + [Fact, WorkItem(19284, "https://github.com/dotnet/roslyn/issues/19284")] + public void TestOptionChangedHandlerInvokedAfterCurrentSolutionChanged() { using var primaryWorkspace = CreateWorkspace(); using var secondaryWorkspace = CreateWorkspace(); @@ -1437,16 +1437,7 @@ public void TestOptionChangedHandlerInvokedAfterCurrentSolutionChanged(bool test primaryWorkspace.GlobalOptions.OptionChanged += OptionService_OptionChanged; // Change workspace options through primary workspace - if (testDeprecatedOptionsSetter) - { -#pragma warning disable CS0618 // Type or member is obsolete - this test ensures that deprecated "Workspace.set_Options" API's functionality is preserved. - primaryWorkspace.Options = primaryWorkspace.Options.WithChangedOption(optionKey, FormattingOptions2.IndentStyle.Block); -#pragma warning restore CS0618 // Type or member is obsolete - } - else - { - primaryWorkspace.SetOptions(primaryWorkspace.Options.WithChangedOption(optionKey, FormattingOptions2.IndentStyle.Block)); - } + primaryWorkspace.Options = primaryWorkspace.Options.WithChangedOption(optionKey, FormattingOptions2.IndentStyle.Block); // Verify current solution and option change for both workspaces. VerifyCurrentSolutionAndOptionChange(primaryWorkspace, beforeSolutionForPrimaryWorkspace); diff --git a/src/EditorFeatures/Core/Options/LegacyGlobalOptionsWorkspaceService.cs b/src/EditorFeatures/Core/Options/LegacyGlobalOptionsWorkspaceService.cs index d7df3989986c8..ebd98a223530e 100644 --- a/src/EditorFeatures/Core/Options/LegacyGlobalOptionsWorkspaceService.cs +++ b/src/EditorFeatures/Core/Options/LegacyGlobalOptionsWorkspaceService.cs @@ -79,9 +79,6 @@ public bool InlineHintsOptionsDisplayAllOverride public CleanCodeGenerationOptionsProvider CleanCodeGenerationOptionsProvider => _provider; - public AutoFormattingOptions GetAutoFormattingOptions(HostLanguageServices languageServices) - => _globalOptions.GetAutoFormattingOptions(languageServices.Language); - public bool GetGenerateEqualsAndGetHashCodeFromMembersGenerateOperators(string language) => _globalOptions.GetOption(s_implementIEquatable, language); diff --git a/src/EditorFeatures/DiagnosticsTestUtilities/SplitComments/AbstractSplitCommentCommandHandlerTests.cs b/src/EditorFeatures/DiagnosticsTestUtilities/SplitComments/AbstractSplitCommentCommandHandlerTests.cs index 20623f233d3b9..b45b259b11b14 100644 --- a/src/EditorFeatures/DiagnosticsTestUtilities/SplitComments/AbstractSplitCommentCommandHandlerTests.cs +++ b/src/EditorFeatures/DiagnosticsTestUtilities/SplitComments/AbstractSplitCommentCommandHandlerTests.cs @@ -52,11 +52,11 @@ private void TestWorker( using var workspace = CreateWorkspace(inputMarkup); - var globalOptions = workspace.ExportProvider.GetExportedValue(); + var globalOptions = workspace.GlobalOptions; var language = workspace.Projects.Single().Language; globalOptions.SetGlobalOption(new OptionKey(SplitCommentOptions.Enabled, language), enabled); - workspace.SetOptions(workspace.Options.WithChangedOption(FormattingOptions.UseTabs, language, useTabs)); + globalOptions.SetGlobalOption(new OptionKey(FormattingOptions.UseTabs, language), useTabs); var document = workspace.Documents.Single(); var view = document.GetTextView(); diff --git a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs index 8cd2fc53e5592..da26fe720287a 100644 --- a/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs +++ b/src/EditorFeatures/Test/Workspaces/ProjectCacheHostServiceFactoryTests.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Options.Providers; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.UnitTests; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; @@ -264,17 +265,15 @@ private class MockHostWorkspaceServices : HostWorkspaceServices { private readonly HostServices _hostServices; private readonly Workspace _workspace; + private readonly ILegacyWorkspaceOptionService _optionService = new MockOptionService(); + private static readonly ITaskSchedulerProvider s_taskSchedulerProvider = new MockTaskSchedulerProvider(); private static readonly IWorkspaceAsynchronousOperationListenerProvider s_asyncListenerProvider = new MockWorkspaceAsynchronousOperationListenerProvider(); - private readonly OptionServiceFactory.OptionService _optionService; public MockHostWorkspaceServices(HostServices hostServices, Workspace workspace) { _hostServices = hostServices; _workspace = workspace; - - var globalOptionService = new GlobalOptionService(workspaceThreadingService: null, ImmutableArray>.Empty, ImmutableArray>.Empty); - _optionService = new OptionServiceFactory.OptionService(globalOptionService); } public override HostServices HostServices => _hostServices; @@ -303,6 +302,26 @@ public override TWorkspaceService GetService() return default; } + + private sealed class MockOptionService : ILegacyWorkspaceOptionService + { + public IGlobalOptionService GlobalOptions { get; } = + new GlobalOptionService(workspaceThreadingService: null, ImmutableArray>.Empty, ImmutableArray>.Empty); + + public void RegisterWorkspace(Workspace workspace) + { + } + + public void UnregisterWorkspace(Workspace workspace) + { + } + + public object GetOption(OptionKey key) + => throw new NotImplementedException(); + + public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) + => throw new NotImplementedException(); + } } } } diff --git a/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs b/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs index b85e8149c6164..9462df03b59d8 100644 --- a/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs +++ b/src/EditorFeatures/TestUtilities/AutomaticCompletion/AbstractAutomaticLineEnderTests.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Editor.UnitTests.Utilities; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Microsoft.VisualStudio.Commanding; @@ -74,7 +75,7 @@ private void Test(string expected, string code, int position, bool useTabs, bool // WPF is required for some reason: https://github.com/dotnet/roslyn/issues/46286 using var workspace = TestWorkspace.Create(Language, compilationOptions: null, parseOptions: null, new[] { markupCode }, composition: EditorTestCompositions.EditorFeaturesWpf); - workspace.SetOptions(workspace.Options.WithChangedOption(FormattingOptions2.UseTabs, Language, useTabs)); + workspace.GlobalOptions.SetGlobalOption(new OptionKey(FormattingOptions2.UseTabs, Language), useTabs); var view = workspace.Documents.Single().GetTextView(); var buffer = workspace.Documents.Single().GetTextBuffer(); diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractArgumentProviderTests`1.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractArgumentProviderTests`1.cs index 222c32d6dc623..5bb6ecacf6ab8 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractArgumentProviderTests`1.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractArgumentProviderTests`1.cs @@ -44,8 +44,6 @@ private protected ReferenceCountedDisposable GetOrCreateWorks protected abstract (SyntaxNode argumentList, ImmutableArray arguments) GetArgumentList(SyntaxToken token); - protected virtual OptionSet WithChangedOptions(OptionSet options) => options; - private protected async Task VerifyDefaultValueAsync( string markup, string? expectedDefaultValue, @@ -58,14 +56,7 @@ private protected async Task VerifyDefaultValueAsync( var code = workspaceFixture.Target.Code; var position = workspaceFixture.Target.Position; - var changedOptions = WithChangedOptions(workspace.Options); - if (options is not null) - { - foreach (var option in options) - changedOptions = changedOptions.WithChangedOption(option.Key, option.Value); - } - - workspace.SetOptions(changedOptions); + options?.SetGlobalOptions(workspace.GlobalOptions); var document = workspaceFixture.Target.UpdateDocument(code, SourceCodeKind.Regular); diff --git a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs index 5a190751cfbd0..188e9fb901b0b 100644 --- a/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs +++ b/src/EditorFeatures/TestUtilities/Completion/AbstractCompletionProviderTests.cs @@ -16,6 +16,7 @@ using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServices; @@ -58,8 +59,8 @@ protected AbstractCompletionProviderTests() MockCompletionSession = new Mock(MockBehavior.Strict); } - protected virtual OptionSet WithChangedNonCompletionOptions(OptionSet options) - => options; + internal virtual OptionsCollection NonCompletionOptions + => null; private CompletionOptions GetCompletionOptions() { @@ -257,7 +258,7 @@ private async Task VerifyAsync( var position = workspaceFixture.Target.Position; // Set options that are not CompletionOptions - workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); + NonCompletionOptions?.SetGlobalOptions(workspace.GlobalOptions); await VerifyWorkerAsync( code, position, expectedItemOrNull, expectedDescriptionOrNull, @@ -274,7 +275,7 @@ protected async Task GetCompletionListAsync(string markup, strin var workspace = workspaceFixture.Target.GetWorkspace(markup, ExportProvider, workspaceKind: workspaceKind); // Set options that are not CompletionOptions - workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); + NonCompletionOptions?.SetGlobalOptions(workspace.GlobalOptions); var currentDocument = workspace.CurrentSolution.GetDocument(workspaceFixture.Target.CurrentDocument.Id); var position = workspaceFixture.Target.Position; @@ -452,7 +453,7 @@ protected virtual async Task VerifyCustomCommitProviderWorkerAsync(string codeBe var workspace = workspaceFixture.Target.GetWorkspace(); // Set options that are not CompletionOptions - workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); + NonCompletionOptions?.SetGlobalOptions(workspace.GlobalOptions); var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); await VerifyCustomCommitProviderCheckResultsAsync(document1, codeBeforeCommit, position, itemToCommit, expectedCodeAfterCommit, commitChar); @@ -574,7 +575,7 @@ private async Task VerifyProviderCommitWorkerAsync(string codeBeforeCommit, int var workspace = workspaceFixture.Target.GetWorkspace(); // Set options that are not CompletionOptions - workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); + NonCompletionOptions?.SetGlobalOptions(workspace.GlobalOptions); var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); await VerifyProviderCommitCheckResultsAsync(document1, position, itemToCommit, expectedCodeAfterCommit, commitChar); diff --git a/src/EditorFeatures/TestUtilities/Extensions/SolutionExtensions.cs b/src/EditorFeatures/TestUtilities/Extensions/SolutionExtensions.cs deleted file mode 100644 index 14c3a186adedb..0000000000000 --- a/src/EditorFeatures/TestUtilities/Extensions/SolutionExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests.Extensions -{ - public static class SolutionExtensions - { - public static Solution WithChangedOptionsFrom(this Solution solution, OptionSet optionSet) - { - var newOptions = solution.Options; - foreach (var option in optionSet.GetChangedOptions(solution.Options)) - { - newOptions = newOptions.WithChangedOption(option, optionSet.GetOption(option)); - } - - return solution.WithOptions(newOptions); - } - } -} diff --git a/src/EditorFeatures/TestUtilities/ExtractInterface/ExtractInterfaceTestState.cs b/src/EditorFeatures/TestUtilities/ExtractInterface/ExtractInterfaceTestState.cs index da77cb8bc205f..9a2a3a01af6ba 100644 --- a/src/EditorFeatures/TestUtilities/ExtractInterface/ExtractInterfaceTestState.cs +++ b/src/EditorFeatures/TestUtilities/ExtractInterface/ExtractInterfaceTestState.cs @@ -47,11 +47,7 @@ public static ExtractInterfaceTestState Create( ? TestWorkspace.CreateCSharp(markup, composition: Composition, compilationOptions: compilationOptions, parseOptions: parseOptions) : TestWorkspace.CreateVisualBasic(markup, composition: Composition, compilationOptions: compilationOptions, parseOptions: parseOptions); - if (options != null) - { - foreach (var kvp in options) - workspace.SetOptions(workspace.Options.WithChangedOption(kvp.Key, kvp.Value)); - } + options?.SetGlobalOptions(workspace.GlobalOptions); return new ExtractInterfaceTestState(workspace); } diff --git a/src/EditorFeatures/TestUtilities/TestOptionsServiceFactory.cs b/src/EditorFeatures/TestUtilities/TestOptionsServiceFactory.cs deleted file mode 100644 index bfcf00dd9c6b2..0000000000000 --- a/src/EditorFeatures/TestUtilities/TestOptionsServiceFactory.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests -{ - [ExportWorkspaceServiceFactory(typeof(IOptionService), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal class TestOptionsServiceFactory : IWorkspaceServiceFactory - { - private readonly IWorkspaceThreadingService? _workspaceThreadingService; - private readonly ImmutableArray> _providers; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestOptionsServiceFactory( - [Import(AllowDefault = true)] IWorkspaceThreadingService? workspaceThreadingService, - [ImportMany] IEnumerable> optionProviders) - { - _workspaceThreadingService = workspaceThreadingService; - _providers = optionProviders.ToImmutableArray(); - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - { - // give out new option service per workspace - return new OptionServiceFactory.OptionService( - new GlobalOptionService(_workspaceThreadingService, _providers, SpecializedCollections.EmptyEnumerable>())); - } - } -} diff --git a/src/EditorFeatures/TestUtilities/TestOptionsServiceWithSharedGlobalOptionsServiceFactory.cs b/src/EditorFeatures/TestUtilities/TestOptionsServiceWithSharedGlobalOptionsServiceFactory.cs deleted file mode 100644 index d253a43ccb712..0000000000000 --- a/src/EditorFeatures/TestUtilities/TestOptionsServiceWithSharedGlobalOptionsServiceFactory.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Editor.UnitTests -{ - /// - /// factory that allows creating multiple test workspaces with shared . - /// This mimics the real product scenarios where all workspaces share the same global options service. - /// Note that majority of unit tests use instead of this factory to ensure options isolation between each test. - /// - [ExportWorkspaceServiceFactory(typeof(IOptionService), ServiceLayer.Test), Shared, PartNotDiscoverable] - internal class TestOptionsServiceWithSharedGlobalOptionsServiceFactory : IWorkspaceServiceFactory - { - private readonly IGlobalOptionService _globalOptionService; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestOptionsServiceWithSharedGlobalOptionsServiceFactory( - [Import(AllowDefault = true)] IWorkspaceThreadingService? workspaceThreadingService, - [ImportMany] IEnumerable> optionProviders) - { - _globalOptionService = new GlobalOptionService(workspaceThreadingService, optionProviders.ToImmutableArray(), SpecializedCollections.EmptyEnumerable>()); - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - { - // give out new option service per workspace, but share the global option service - return new OptionServiceFactory.OptionService(_globalOptionService); - } - } -} diff --git a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb index 2b2bc60c33988..576480202a7e8 100644 --- a/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb +++ b/src/EditorFeatures/VisualBasicTest/EndConstructGeneration/EndConstructCommandHandlerTests.vb @@ -12,7 +12,7 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.EndConstructGenera <[UseExportProvider]> Public Class EndConstructCommandHandlerTests Private ReadOnly _endConstructServiceMock As New Mock(Of IEndConstructGenerationService)(MockBehavior.Strict) - Private ReadOnly _featureOptions As New Mock(Of IOptionService)(MockBehavior.Strict) + Private ReadOnly _featureOptions As New Mock(Of ILegacyWorkspaceOptionService)(MockBehavior.Strict) Private ReadOnly _textViewMock As New Mock(Of ITextView)(MockBehavior.Strict) Private ReadOnly _textBufferMock As New Mock(Of ITextBuffer)(MockBehavior.Strict) diff --git a/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet.cs b/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet.cs index b5439568e4de0..f48419815e699 100644 --- a/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet.cs +++ b/src/Features/Core/Portable/Diagnostics/AnalyzerConfigOptionSet.cs @@ -37,7 +37,7 @@ private protected override object GetOptionCore(OptionKey optionKey) public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) => throw new NotImplementedException(); - private protected override AnalyzerConfigOptions CreateAnalyzerConfigOptions(IOptionService optionService, string? language) + private protected override AnalyzerConfigOptions CreateAnalyzerConfigOptions(ILegacyWorkspaceOptionService optionService, string? language) { if (_optionSet is null) { diff --git a/src/Tools/ExternalAccess/Razor/RazorCSharpFormattingInteractionService.cs b/src/Tools/ExternalAccess/Razor/RazorCSharpFormattingInteractionService.cs index 5723d227aa6cc..567ce64e44f8d 100644 --- a/src/Tools/ExternalAccess/Razor/RazorCSharpFormattingInteractionService.cs +++ b/src/Tools/ExternalAccess/Razor/RazorCSharpFormattingInteractionService.cs @@ -23,44 +23,6 @@ namespace Microsoft.CodeAnalysis.ExternalAccess.Razor /// internal static class RazorCSharpFormattingInteractionService { - /// - /// Returns the text changes necessary to format the document after the user enters a - /// character. The position provided is the position of the caret in the document after - /// the character been inserted into the document. - /// - [Obsolete("Use the other overload")] - public static async Task> GetFormattingChangesAsync( - Document document, - char typedChar, - int position, - DocumentOptionSet documentOptions, - CancellationToken cancellationToken) - { - Contract.ThrowIfFalse(document.Project.Language is LanguageNames.CSharp); - var formattingService = document.GetRequiredLanguageService(); - var documentSyntax = await ParsedDocument.CreateAsync(document, cancellationToken).ConfigureAwait(false); - - if (!formattingService.ShouldFormatOnTypedCharacter(documentSyntax, typedChar, position, cancellationToken)) - { - return ImmutableArray.Empty; - } - - var languageServices = document.Project.LanguageServices; - - var services = document.Project.Solution.Workspace.Services; - var globalOptions = services.GetRequiredService(); - var optionsProvider = (OptionsProvider)globalOptions.CleanCodeGenerationOptionsProvider; - var fallbackOptions = await optionsProvider.GetOptionsAsync(languageServices, cancellationToken).ConfigureAwait(false); - var optionService = services.GetRequiredService(); - var configOptions = documentOptions.AsAnalyzerConfigOptions(optionService, document.Project.Language); - - var indentationOptions = new IndentationOptions(formattingService.GetFormattingOptions(configOptions, fallbackOptions)) - { - AutoFormattingOptions = globalOptions.GetAutoFormattingOptions(languageServices) - }; - - return formattingService.GetFormattingChangesOnTypedCharacter(documentSyntax, position, indentationOptions, cancellationToken); - } /// /// Returns the text changes necessary to format the document after the user enters a diff --git a/src/Tools/ExternalAccess/Razor/RazorGlobalOptions.cs b/src/Tools/ExternalAccess/Razor/RazorGlobalOptions.cs index 82b6380a59716..c9a476132acbc 100644 --- a/src/Tools/ExternalAccess/Razor/RazorGlobalOptions.cs +++ b/src/Tools/ExternalAccess/Razor/RazorGlobalOptions.cs @@ -67,12 +67,11 @@ public T GetOption(Option option) public ImmutableArray GetOptions(ImmutableArray optionKeys) => throw new NotImplementedException(); public IEnumerable GetRegisteredOptions() => throw new NotImplementedException(); public ImmutableHashSet GetRegisteredSerializableOptions(ImmutableHashSet languages) => throw new NotImplementedException(); - public SerializableOptionSet GetSerializableOptionsSnapshot(ImmutableHashSet languages, IOptionService optionService) => throw new NotImplementedException(); public void RefreshOption(OptionKey optionKey, object? newValue) => throw new NotImplementedException(); public void RegisterWorkspace(Workspace workspace) => throw new NotImplementedException(); public void SetGlobalOption(OptionKey optionKey, object? value) => throw new NotImplementedException(); public void SetGlobalOptions(ImmutableArray optionKeys, ImmutableArray values) => throw new NotImplementedException(); - public void SetOptions(OptionSet optionSet) => throw new NotImplementedException(); + public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) => throw new NotImplementedException(); public bool TryMapEditorConfigKeyToOption(string key, string? language, [NotNullWhen(true)] out IEditorConfigStorageLocation2? storageLocation, out OptionKey optionKey) => throw new NotImplementedException(); public void UnregisterWorkspace(Workspace workspace) => throw new NotImplementedException(); } diff --git a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs b/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs deleted file mode 100644 index 55ccbc21b45a8..0000000000000 --- a/src/VisualStudio/CSharp/Test/PersistentStorage/Mocks/OptionServiceMock.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Options; - -namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices.Mocks -{ - internal class OptionServiceMock : IOptionService - { -#pragma warning disable 67 - public event EventHandler OptionChanged; -#pragma warning restore 67 - - // Feel free to add other option storages - private readonly IDictionary _optionsByOption; - - public OptionServiceMock(IDictionary optionsByOption) - { - _optionsByOption = optionsByOption; - } - - public object GetOption(OptionKey optionKey) - { - throw new NotImplementedException(); - } - - public T GetOption(Option option) - { - return (T)_optionsByOption[option]; - } - - public T GetOption(Option2 option) - { - return (T)_optionsByOption[option]; - } - - public T GetOption(PerLanguageOption option, string languageName) - { - throw new NotImplementedException(); - } - - public T GetOption(PerLanguageOption2 option, string languageName) - { - throw new NotImplementedException(); - } - - public SerializableOptionSet GetOptions() - { - throw new NotImplementedException(); - } - - public SerializableOptionSet GetSerializableOptionsSnapshot(ImmutableHashSet languages) - { - throw new NotImplementedException(); - } - - public IEnumerable GetRegisteredOptions() - { - throw new NotImplementedException(); - } - - public bool TryMapEditorConfigKeyToOption(string key, string language, [NotNullWhen(true)] out IEditorConfigStorageLocation2 storageLocation, out OptionKey optionKey) - { - throw new NotImplementedException(); - } - - public ImmutableHashSet GetRegisteredSerializableOptions(ImmutableHashSet languages) - { - throw new NotImplementedException(); - } - - public void SetOptions(OptionSet optionSet) - { - Equals(null, null); - throw new NotImplementedException(); - } - - public Task GetUpdatedOptionSetForDocumentAsync(Document document, OptionSet optionSet, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public void RegisterWorkspace(Workspace workspace) - { - throw new NotImplementedException(); - } - - public void UnregisterWorkspace(Workspace workspace) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPage.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPage.cs index 27ff526ad6bcf..6467604de0811 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPage.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPage.cs @@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Options [System.ComponentModel.DesignerCategory("code")] // this must be fully qualified internal abstract class AbstractOptionPage : UIElementDialogPage { - private static IOptionService s_optionService; + private static ILegacyWorkspaceOptionService s_optionService; private static OptionStore s_optionStore; private static bool s_needsToUpdateOptionStore = true; @@ -30,8 +30,8 @@ private void EnsureOptionPageCreated() { var componentModel = (IComponentModel)this.Site.GetService(typeof(SComponentModel)); var workspace = componentModel.GetService(); - s_optionService = workspace.Services.GetService(); - s_optionStore = new OptionStore(s_optionService.GetOptions(), s_optionService.GetRegisteredOptions()); + s_optionService = workspace.Services.GetService(); + s_optionStore = new OptionStore(new SolutionOptionSet(s_optionService), s_optionService.GlobalOptions.GetRegisteredOptions()); } if (pageControl == null) @@ -59,8 +59,8 @@ protected override void OnActivate(System.ComponentModel.CancelEventArgs e) if (s_needsToUpdateOptionStore) { // Reset the option store to the current state of the options. - s_optionStore.SetOptions(s_optionService.GetOptions()); - s_optionStore.SetRegisteredOptions(s_optionService.GetRegisteredOptions()); + s_optionStore.SetOptions(new SolutionOptionSet(s_optionService)); + s_optionStore.SetRegisteredOptions(s_optionService.GlobalOptions.GetRegisteredOptions()); s_needsToUpdateOptionStore = false; } @@ -111,13 +111,13 @@ public override void SaveSettingsToStorage() pageControl.OnSave(); // Save the changes that were accumulated in the option store. - var oldOptions = s_optionService.GetOptions(); - var newOptions = s_optionStore.GetOptions(); + var oldOptions = new SolutionOptionSet(s_optionService); + var newOptions = (SolutionOptionSet)s_optionStore.GetOptions(); // Must log the option change before setting the new option values via s_optionService, // otherwise oldOptions and newOptions would be identical and nothing will be logged. OptionLogger.Log(oldOptions, newOptions); - s_optionService.SetOptions(newOptions); + s_optionService.SetOptions(newOptions, newOptions.GetChangedOptions()); // Make sure we load the next time a page is activated, in case that options changed // programmatically between now and the next time the page is activated diff --git a/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs b/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs index ae8250028a21a..13119f6bab72c 100644 --- a/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs +++ b/src/VisualStudio/Core/Impl/Options/AbstractOptionPreviewViewModel.cs @@ -139,7 +139,7 @@ public void UpdatePreview(string text) var document = project.AddDocument("document", SourceText.From(text, Encoding.UTF8)); var fallbackFormattingOptions = _globalOptions.GetSyntaxFormattingOptions(document.Project.LanguageServices); - var optionService = workspace.Services.GetRequiredService(); + var optionService = workspace.Services.GetRequiredService(); var configOptions = OptionStore.GetOptions().AsAnalyzerConfigOptions(optionService, document.Project.Language); var formattingService = document.GetRequiredLanguageService(); var formattingOptions = formattingService.GetFormattingOptions(configOptions, fallbackFormattingOptions); diff --git a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs index 652a15e78d792..79c463652b4f5 100644 --- a/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs +++ b/src/VisualStudio/Core/Test.Next/Remote/SnapshotSerializationTests.cs @@ -53,9 +53,6 @@ private static Workspace CreateWorkspace(Type[] additionalParts = null) internal static Solution CreateFullSolution(Workspace workspace) { var solution = workspace.CurrentSolution; - var languages = ImmutableHashSet.Create(LanguageNames.CSharp, LanguageNames.VisualBasic); - var solutionOptions = solution.Workspace.Services.GetRequiredService().GetSerializableOptionsSnapshot(languages); - solution = solution.WithOptions(solutionOptions); var csCode = "class A { }"; var project1 = solution.AddProject("Project", "Project.dll", LanguageNames.CSharp); @@ -371,52 +368,6 @@ public async Task Workspace_RoundTrip_Test_Desktop() validator.SolutionStateEqual(solutionObject2, solutionObject3); } - [Fact] - public async Task OptionSet_Serialization() - { - using var workspace = CreateWorkspace() - .CurrentSolution.AddProject("Project1", "Project.dll", LanguageNames.CSharp) - .Solution.AddProject("Project2", "Project2.dll", LanguageNames.VisualBasic) - .Solution.Workspace; - await VerifyOptionSetsAsync(workspace, _ => { }).ConfigureAwait(false); - } - - [Fact] - public async Task OptionSet_Serialization_CustomValue() - { - using var workspace = CreateWorkspace(); - - var newQualifyFieldAccessValue = new CodeStyleOption2(false, NotificationOption2.Error); - var newQualifyMethodAccessValue = new CodeStyleOption2(true, NotificationOption2.Warning); - var newVarWhenTypeIsApparentValue = new CodeStyleOption2(false, NotificationOption2.Suggestion); - var newPreferIntrinsicPredefinedTypeKeywordInMemberAccessValue = new CodeStyleOption2(true, NotificationOption2.Silent); - - workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(workspace.Options - .WithChangedOption(CodeStyleOptions2.QualifyFieldAccess, LanguageNames.CSharp, newQualifyFieldAccessValue) - .WithChangedOption(CodeStyleOptions2.QualifyMethodAccess, LanguageNames.VisualBasic, newQualifyMethodAccessValue) - .WithChangedOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent, newVarWhenTypeIsApparentValue) - .WithChangedOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, LanguageNames.VisualBasic, newPreferIntrinsicPredefinedTypeKeywordInMemberAccessValue))); - - var validator = new SerializationValidator(workspace.Services); - - await VerifyOptionSetsAsync(workspace, VerifyOptions).ConfigureAwait(false); - - void VerifyOptions(OptionSet options) - { - var actualQualifyFieldAccessValue = options.GetOption(CodeStyleOptions2.QualifyFieldAccess, LanguageNames.CSharp); - Assert.Equal(newQualifyFieldAccessValue, actualQualifyFieldAccessValue); - - var actualQualifyMethodAccessValue = options.GetOption(CodeStyleOptions2.QualifyMethodAccess, LanguageNames.VisualBasic); - Assert.Equal(newQualifyMethodAccessValue, actualQualifyMethodAccessValue); - - var actualVarWhenTypeIsApparentValue = options.GetOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent); - Assert.Equal(newVarWhenTypeIsApparentValue, actualVarWhenTypeIsApparentValue); - - var actualPreferIntrinsicPredefinedTypeKeywordInMemberAccessValue = options.GetOption(CodeStyleOptions2.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, LanguageNames.VisualBasic); - Assert.Equal(newPreferIntrinsicPredefinedTypeKeywordInMemberAccessValue, actualPreferIntrinsicPredefinedTypeKeywordInMemberAccessValue); - } - } - [Fact] public void Missing_Metadata_Serialization_Test() { @@ -575,17 +526,6 @@ public async Task UnknownLanguageTest() var recovered = await validator.GetSolutionAsync(snapshot).ConfigureAwait(false); } - [Fact, WorkItem(44791, "https://github.com/dotnet/roslyn/issues/44791")] - public async Task UnknownLanguageOptionsTest() - { - using var workspace = CreateWorkspace(new[] { typeof(NoCompilationLanguageServiceFactory) }); - var project = workspace.CurrentSolution.AddProject("Project", "Project.dll", NoCompilationConstants.LanguageName) - .Solution.AddProject("Project2", "Project2.dll", LanguageNames.CSharp); - workspace.TryApplyChanges(project.Solution); - - await VerifyOptionSetsAsync(workspace, verifyOptionValues: _ => { }); - } - [Fact] public async Task EmptyAssetChecksumTest() { @@ -734,35 +674,6 @@ void VerifyOptions(CompilationOptions originalOptions) } } - private static async Task VerifyOptionSetsAsync(Workspace workspace, Action verifyOptionValues) - { - var solution = workspace.CurrentSolution; - - verifyOptionValues(workspace.Options); - verifyOptionValues(solution.Options); - - var validator = new SerializationValidator(workspace.Services); - - using var scope = await validator.AssetStorage.StoreAssetsAsync(solution, CancellationToken.None).ConfigureAwait(false); - var checksum = scope.SolutionChecksum; - var solutionObject = await validator.GetValueAsync(checksum).ConfigureAwait(false); - - await validator.VerifyChecksumInServiceAsync(solutionObject.Attributes, WellKnownSynchronizationKind.SolutionAttributes); - - var recoveredSolution = await validator.GetSolutionAsync(scope); - - // option should be exactly same - Assert.Equal(0, recoveredSolution.Options.GetChangedOptions(workspace.Options).Count()); - - verifyOptionValues(workspace.Options); - verifyOptionValues(recoveredSolution.Options); - - // checksum for recovered solution should be the same. - using var recoveredScope = await validator.AssetStorage.StoreAssetsAsync(recoveredSolution, CancellationToken.None).ConfigureAwait(false); - var recoveredChecksum = recoveredScope.SolutionChecksum; - Assert.Equal(checksum, recoveredChecksum); - } - private static SolutionAsset CloneAsset(ISerializerService serializer, SolutionAsset asset) { using var stream = SerializableBytes.CreateWritableStream(); diff --git a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs index 866a6632e6fd4..fb5317ccf8fc6 100644 --- a/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs +++ b/src/VisualStudio/Core/Test.Next/Services/ServiceHubServicesTests.cs @@ -39,9 +39,6 @@ public class ServiceHubServicesTests private static TestWorkspace CreateWorkspace(Type[] additionalParts = null) => new TestWorkspace(composition: FeaturesTestCompositions.Features.WithTestHostParts(TestHost.OutOfProcess).AddParts(additionalParts)); - private static Solution WithChangedOptionsFromRemoteWorkspace(Solution solution, RemoteWorkspace remoteWorkpace) - => solution.WithChangedOptionsFrom(remoteWorkpace.Options); - [Fact] public async Task TestRemoteHostSynchronize() { @@ -59,8 +56,6 @@ public async Task TestRemoteHostSynchronize() var remoteWorkpace = client.GetRemoteWorkspace(); - solution = WithChangedOptionsFromRemoteWorkspace(solution, remoteWorkpace); - Assert.Equal( await solution.State.GetChecksumAsync(CancellationToken.None), await remoteWorkpace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); @@ -271,15 +266,12 @@ public async Task TestUnknownProject() // See "RemoteSupportedLanguages.IsSupported" Assert.Empty(remoteWorkspace.CurrentSolution.Projects); - solution = WithChangedOptionsFromRemoteWorkspace(solution, remoteWorkspace); - // No serializable remote options affect options checksum, so the checksums should match. Assert.Equal( await solution.State.GetChecksumAsync(CancellationToken.None), await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); solution = solution.RemoveProject(solution.ProjectIds.Single()); - solution = WithChangedOptionsFromRemoteWorkspace(solution, remoteWorkspace); Assert.Equal( await solution.State.GetChecksumAsync(CancellationToken.None), @@ -304,8 +296,6 @@ public async Task TestRemoteHostSynchronizeIncrementalUpdate(bool applyInBatch) await UpdatePrimaryWorkspace(client, solution); await VerifyAssetStorageAsync(client, solution); - solution = WithChangedOptionsFromRemoteWorkspace(solution, remoteWorkspace); - Assert.Equal( await solution.State.GetChecksumAsync(CancellationToken.None), await remoteWorkspace.CurrentSolution.State.GetChecksumAsync(CancellationToken.None)); diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs index 2f9a2ebdc04fc..13f630ee4d298 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/InProcess/VisualStudioWorkspace_InProc.cs @@ -59,71 +59,16 @@ public bool IsPrettyListingOn(string languageName) => _globalOptions.GetOption(FeatureOnOffOptions.PrettyListing, languageName); public void SetPrettyListing(string languageName, bool value) - => InvokeOnUIThread(cancellationToken => - { - _globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.PrettyListing, languageName), value); - }); + => InvokeOnUIThread(_ => _globalOptions.SetGlobalOption(new OptionKey(FeatureOnOffOptions.PrettyListing, languageName), value)); public void SetFileScopedNamespaces(bool value) - => InvokeOnUIThread(cancellationToken => - { - _visualStudioWorkspace.SetOptions(_visualStudioWorkspace.Options.WithChangedOption( - new OptionKey(GetOption("NamespaceDeclarations", "CSharpCodeStyleOptions")), - new CodeStyleOption2(value - ? NamespaceDeclarationPreference.FileScoped - : NamespaceDeclarationPreference.BlockScoped, - NotificationOption2.Suggestion))); - }); - - public void SetPerLanguageOption(string optionName, string feature, string language, object value) - { - var option = GetOption(optionName, feature); - var result = GetValue(value, option); - var optionKey = new OptionKey(option, language); - SetOption(optionKey, result); - } - - public void SetOption(string optionName, string feature, object value) - { - var option = GetOption(optionName, feature); - var result = GetValue(value, option); - var optionKey = new OptionKey(option); - SetOption(optionKey, result); - } + => InvokeOnUIThread(_ => _globalOptions.SetGlobalOption( + new OptionKey(Microsoft.CodeAnalysis.CSharp.CodeStyle.CSharpCodeStyleOptions.NamespaceDeclarations), + new CodeStyleOption2(value ? NamespaceDeclarationPreference.FileScoped : NamespaceDeclarationPreference.BlockScoped, NotificationOption2.Suggestion))); public void SetGlobalOption(WellKnownGlobalOption option, string? language, object? value) => InvokeOnUIThread(_ => _globalOptions.SetGlobalOption(option.GetKey(language), value)); - private static object GetValue(object value, IOption option) - { - object result; - if (value is string stringValue) - { - result = TypeDescriptor.GetConverter(option.Type).ConvertFromString(stringValue); - } - else - { - result = value; - } - - return result; - } - - private IOption GetOption(string optionName, string feature) - { - var optionService = _visualStudioWorkspace.Services.GetRequiredService(); - var option = optionService.GetRegisteredOptions().FirstOrDefault(o => o.Feature == feature && o.Name == optionName); - if (option == null) - { - throw new Exception($"Failed to find option with feature name '{feature}' and option name '{optionName}'"); - } - - return option; - } - - private void SetOption(OptionKey optionKey, object? result) - => _visualStudioWorkspace.SetOptions(_visualStudioWorkspace.Options.WithChangedOption(optionKey, result)); - public void WaitForAsyncOperations(TimeSpan timeout, string featuresToWaitFor, bool waitForWorkspaceFirst = true) { if (waitForWorkspaceFirst || featuresToWaitFor == FeatureAttribute.Workspace) @@ -202,12 +147,12 @@ void ResetOption(IOption option) { if (option is IPerLanguageOption) { - SetOption(new OptionKey(option, LanguageNames.CSharp), option.DefaultValue); - SetOption(new OptionKey(option, LanguageNames.VisualBasic), option.DefaultValue); + _globalOptions.SetGlobalOption(new OptionKey(option, LanguageNames.CSharp), option.DefaultValue); + _globalOptions.SetGlobalOption(new OptionKey(option, LanguageNames.VisualBasic), option.DefaultValue); } else { - SetOption(new OptionKey(option), option.DefaultValue); + _globalOptions.SetGlobalOption(new OptionKey(option), option.DefaultValue); } } } @@ -224,14 +169,5 @@ public void CleanUpWaitingService() GetWaitingService().EnableActiveTokenTracking(true); }); - - public void SetFeatureOption(string feature, string optionName, string? language, string? valueString) - => InvokeOnUIThread(cancellationToken => - { - var option = GetOption(optionName, feature); - - var value = TypeDescriptor.GetConverter(option.Type).ConvertFromString(valueString); - SetOption(new OptionKey(option, language), value); - }); } } diff --git a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs index b6c14de757106..521f8254e039f 100644 --- a/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs +++ b/src/VisualStudio/IntegrationTest/TestUtilities/OutOfProcess/VisualStudioWorkspace_OutOfProc.cs @@ -31,12 +31,6 @@ public bool IsPrettyListingOn(string languageName) public void SetPrettyListing(string languageName, bool value) => _inProc.SetPrettyListing(languageName, value); - public void SetPerLanguageOption(string optionName, string feature, string language, object value) - => _inProc.SetPerLanguageOption(optionName, feature, language, value); - - public void SetOption(string optionName, string feature, object value) - => _inProc.SetOption(optionName, feature, value); - public void WaitForAsyncOperations(TimeSpan timeout, string featuresToWaitFor, bool waitForWorkspaceFirst = true) => _inProc.WaitForAsyncOperations(timeout, featuresToWaitFor, waitForWorkspaceFirst); @@ -89,9 +83,6 @@ public void SetFullSolutionAnalysis(bool value) public void SetFileScopedNamespaces(bool value) => _inProc.SetFileScopedNamespaces(value); - public void SetFeatureOption(string feature, string optionName, string? language, string? valueString) - => _inProc.SetFeatureOption(feature, optionName, language, valueString); - public void SetGlobalOption(WellKnownGlobalOption option, string? language, object? value) => _inProc.SetGlobalOption(option, language, value); } diff --git a/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj b/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj index 68eb3cc4163c1..a227bb1a0985e 100644 --- a/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj +++ b/src/Workspaces/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.Workspaces.csproj @@ -51,6 +51,7 @@ + diff --git a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption.cs b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption.cs index b5c8bba1c9102..20c140efef8d6 100644 --- a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption.cs +++ b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOption.cs @@ -13,11 +13,6 @@ namespace Microsoft.CodeAnalysis.CodeStyle /// public sealed class CodeStyleOption : ICodeStyleOption, IEquatable> { - static CodeStyleOption() - { - ObjectBinder.RegisterTypeReader(typeof(CodeStyleOption), ReadFrom); - } - private readonly CodeStyleOption2 _codeStyleOptionImpl; public static CodeStyleOption Default => new(default, NotificationOption.Silent); @@ -37,7 +32,6 @@ public T Value set => throw new InvalidOperationException(); } - bool IObjectWritable.ShouldReuseInSerialization => _codeStyleOptionImpl.ShouldReuseInSerialization; object ICodeStyleOption.Value => this.Value; NotificationOption2 ICodeStyleOption.Notification => _codeStyleOptionImpl.Notification; ICodeStyleOption ICodeStyleOption.WithValue(object value) => new CodeStyleOption((T)value, Notification); @@ -61,12 +55,6 @@ public NotificationOption Notification public static CodeStyleOption FromXElement(XElement element) => new(CodeStyleOption2.FromXElement(element)); - void IObjectWritable.WriteTo(ObjectWriter writer) - => _codeStyleOptionImpl.WriteTo(writer); - - internal static CodeStyleOption ReadFrom(ObjectReader reader) - => new(CodeStyleOption2.ReadFrom(reader)); - public bool Equals(CodeStyleOption other) => _codeStyleOptionImpl.Equals(other?._codeStyleOptionImpl); diff --git a/src/Workspaces/Core/Portable/Formatting/Formatter.cs b/src/Workspaces/Core/Portable/Formatting/Formatter.cs index 0d7582888c9b5..e2e7250932a9d 100644 --- a/src/Workspaces/Core/Portable/Formatting/Formatter.cs +++ b/src/Workspaces/Core/Portable/Formatting/Formatter.cs @@ -319,7 +319,7 @@ internal static IList GetFormattedTextChanges(SyntaxNode node, IEnum internal static SyntaxFormattingOptions GetFormattingOptions(Workspace workspace, OptionSet? optionSet, string language) { var syntaxFormattingService = workspace.Services.GetRequiredLanguageService(language); - var optionService = workspace.Services.GetRequiredService(); + var optionService = workspace.Services.GetRequiredService(); var configOptionSet = (optionSet ?? workspace.CurrentSolution.Options).AsAnalyzerConfigOptions(optionService, language); return syntaxFormattingService.GetFormattingOptions(configOptionSet, fallbackOptions: null); } @@ -327,7 +327,7 @@ internal static SyntaxFormattingOptions GetFormattingOptions(Workspace workspace #pragma warning disable RS0030 // Do not used banned APIs (backwards compatibility) internal static async ValueTask<(SyntaxFormattingOptions? Syntax, LineFormattingOptions Line)> GetFormattingOptionsAsync(Document document, OptionSet? optionSet, CancellationToken cancellationToken) { - var optionService = document.Project.Solution.Workspace.Services.GetRequiredService(); + var optionService = document.Project.Solution.Workspace.Services.GetRequiredService(); var configOptionSet = (optionSet ?? await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false)).AsAnalyzerConfigOptions(optionService, document.Project.Language); LineFormattingOptions lineFormattingOptions; @@ -370,7 +370,7 @@ public static async Task OrganizeImportsAsync(Document document, Cance #pragma warning disable RS0030 // Do not used banned APIs (backwards compatibility) internal static async ValueTask GetOrganizeImportsOptionsAsync(Document document, CancellationToken cancellationToken) { - var optionService = document.Project.Solution.Workspace.Services.GetRequiredService(); + var optionService = document.Project.Solution.Workspace.Services.GetRequiredService(); var configOptionSet = (await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false)).AsAnalyzerConfigOptions(optionService, document.Project.Language); return configOptionSet.GetOrganizeImportsOptions(fallbackOptions: null); } diff --git a/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs b/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs index 287b1c835a5e9..ea8d8f8dd2247 100644 --- a/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs +++ b/src/Workspaces/Core/Portable/Options/DocumentOptionSet.cs @@ -108,7 +108,7 @@ public DocumentOptionSet WithChangedOption(PerLanguageOption option, T val internal DocumentOptionSet WithChangedOption(PerLanguageOption2 option, T value) => (DocumentOptionSet)WithChangedOption(option, _language, value); - private protected override AnalyzerConfigOptions CreateAnalyzerConfigOptions(IOptionService optionService, string? language) + private protected override AnalyzerConfigOptions CreateAnalyzerConfigOptions(ILegacyWorkspaceOptionService optionService, string? language) { Debug.Assert((language ?? _language) == _language, $"Use of a {nameof(DocumentOptionSet)} is not expected to differ from the language it was constructed with."); return base.CreateAnalyzerConfigOptions(optionService, language ?? _language); diff --git a/src/Workspaces/Core/Portable/Options/GlobalOptionService.cs b/src/Workspaces/Core/Portable/Options/GlobalOptionService.cs index 3d8b94023f2f1..630607de047d2 100644 --- a/src/Workspaces/Core/Portable/Options/GlobalOptionService.cs +++ b/src/Workspaces/Core/Portable/Options/GlobalOptionService.cs @@ -32,7 +32,6 @@ internal sealed class GlobalOptionService : IGlobalOptionService private readonly Lazy> _lazyAllOptions; private readonly ImmutableArray> _optionPersisterProviders; private readonly ImmutableDictionary>> _serializableOptionsByLanguage; - private readonly HashSet _forceComputedLanguages = new(); // access is interlocked private ImmutableArray _registeredWorkspaces; @@ -261,61 +260,6 @@ ImmutableHashSet GetSerializableOptionsForLanguage(string language) } } - /// - /// Gets force computed serializable options with prefetched values for all the registered options applicable to the given by quering the option persisters. - /// - public SerializableOptionSet GetSerializableOptionsSnapshot(ImmutableHashSet languages, IOptionService optionService) - { - Debug.Assert(languages.All(RemoteSupportedLanguages.IsSupported)); - var serializableOptions = GetRegisteredSerializableOptions(languages); - var serializableOptionValues = GetSerializableOptionValues(serializableOptions, languages); - var changedOptionsKeysSerializable = _changedOptionKeys - .Where(key => serializableOptions.Contains(key.Option) && (!key.Option.IsPerLanguage || languages.Contains(key.Language!))) - .ToImmutableHashSet(); - return new SerializableOptionSet(optionService, serializableOptionValues, changedOptionsKeysSerializable); - } - - private ImmutableDictionary GetSerializableOptionValues(ImmutableHashSet optionKeys, ImmutableHashSet languages) - { - if (optionKeys.IsEmpty) - { - return ImmutableDictionary.Empty; - } - - // Ensure the option persisters are available before taking the global lock - var persisters = GetOptionPersisters(); - - lock (_gate) - { - // Force compute the option values for languages, if required. - if (!languages.All(_forceComputedLanguages.Contains)) - { - foreach (var option in optionKeys) - { - if (!option.IsPerLanguage) - { - var key = new OptionKey(option); - var _ = GetOption_NoLock(key, persisters); - continue; - } - - foreach (var language in languages) - { - var key = new OptionKey(option, language); - var _ = GetOption_NoLock(key, persisters); - } - } - - _forceComputedLanguages.AddRange(languages); - } - - return ImmutableDictionary.CreateRange(_currentValues - .Where(kvp => optionKeys.Contains(kvp.Key.Option) && - (!kvp.Key.Option.IsPerLanguage || - languages.Contains(kvp.Key.Language!)))); - } - } - public T GetOption(Option option) => OptionsHelpers.GetOption(option, _getOption); @@ -419,25 +363,18 @@ public void SetGlobalOptions(ImmutableArray optionKeys, ImmutableArra RaiseOptionChangedEvent(changedOptions); } - public void SetOptions(OptionSet optionSet) + public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) { - var changedOptionKeys = optionSet switch - { - null => throw new ArgumentNullException(nameof(optionSet)), - SerializableOptionSet serializableOptionSet => serializableOptionSet.GetChangedOptions(), - _ => throw new ArgumentException(WorkspacesResources.Options_did_not_come_from_specified_Solution, paramName: nameof(optionSet)) - }; - var changedOptions = new List(); lock (_gate) { - foreach (var optionKey in changedOptionKeys) + foreach (var optionKey in optionKeys) { var newValue = optionSet.GetOption(optionKey); - var currentValue = this.GetOption(optionKey); + var currentValue = GetOption(optionKey); - if (object.Equals(currentValue, newValue)) + if (Equals(currentValue, newValue)) { // Identical, so nothing is changing continue; diff --git a/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs b/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs index dfda41db85ad2..b980dd27291f0 100644 --- a/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs +++ b/src/Workspaces/Core/Portable/Options/IGlobalOptionService.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Options /// all workspaces/services). /// /// In general you should not import this type directly, and should instead get an - /// from + /// from /// internal interface IGlobalOptionService { @@ -56,7 +56,7 @@ internal interface IGlobalOptionService /// Applies a set of options. /// If any option changed its value invokes registered option persisters, updates current solutions of all registered workspaces and triggers . /// - void SetOptions(OptionSet optionSet); + void SetOptions(OptionSet optionSet, IEnumerable optionKeys); /// /// Sets and persists the value of a global option. @@ -74,11 +74,6 @@ internal interface IGlobalOptionService /// void SetGlobalOptions(ImmutableArray optionKeys, ImmutableArray values); - /// - /// Gets force computed serializable options snapshot with prefetched values for the registered options applicable to the given by quering the option persisters. - /// - SerializableOptionSet GetSerializableOptionsSnapshot(ImmutableHashSet languages, IOptionService optionService); - /// /// Returns the set of all registered options. /// diff --git a/src/Workspaces/Core/Portable/Options/ILegacyGlobalOptionsWorkspaceService.cs b/src/Workspaces/Core/Portable/Options/ILegacyGlobalOptionsWorkspaceService.cs index f9ca403cfe28a..ee3ed1517a9be 100644 --- a/src/Workspaces/Core/Portable/Options/ILegacyGlobalOptionsWorkspaceService.cs +++ b/src/Workspaces/Core/Portable/Options/ILegacyGlobalOptionsWorkspaceService.cs @@ -29,7 +29,6 @@ internal interface ILegacyGlobalOptionsWorkspaceService : IWorkspaceService public bool GetGenerateConstructorFromMembersOptionsAddNullChecks(string language); public void SetGenerateConstructorFromMembersOptionsAddNullChecks(string language, bool value); - public AutoFormattingOptions GetAutoFormattingOptions(HostLanguageServices languageServices); public CleanCodeGenerationOptionsProvider CleanCodeGenerationOptionsProvider { get; } } } diff --git a/src/Workspaces/Core/Portable/Options/ILegacyWorkspaceOptionService.cs b/src/Workspaces/Core/Portable/Options/ILegacyWorkspaceOptionService.cs new file mode 100644 index 0000000000000..3714664667bd4 --- /dev/null +++ b/src/Workspaces/Core/Portable/Options/ILegacyWorkspaceOptionService.cs @@ -0,0 +1,28 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host; + +namespace Microsoft.CodeAnalysis.Options; + +/// +/// Only used by and to implement legacy public APIs: +/// and . +/// +internal interface ILegacyWorkspaceOptionService : IWorkspaceService +{ + IGlobalOptionService GlobalOptions { get; } + + void RegisterWorkspace(Workspace workspace); + void UnregisterWorkspace(Workspace workspace); + + object? GetOption(OptionKey key); + void SetOptions(OptionSet optionSet, IEnumerable optionKeys); +} diff --git a/src/Workspaces/Core/Portable/Options/IOptionService.cs b/src/Workspaces/Core/Portable/Options/IOptionService.cs deleted file mode 100644 index 7eee041732f06..0000000000000 --- a/src/Workspaces/Core/Portable/Options/IOptionService.cs +++ /dev/null @@ -1,88 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; - -namespace Microsoft.CodeAnalysis.Options -{ - /// - /// Provides services for reading and writing options. This will provide support for - /// customizations workspaces need to perform around options. Note that - /// options will normally still be offered through - /// implementations of this. However, implementations may customize things differently - /// depending on their needs. - /// - internal interface IOptionService : IWorkspaceService - { - /// - /// Gets the current value of the specific option. - /// - T? GetOption(Option option); - - /// - /// Gets the current value of the specific option. - /// - T? GetOption(Option2 option); - - /// - /// Gets the current value of the specific option. - /// - T? GetOption(PerLanguageOption option, string? languageName); - - /// - /// Gets the current value of the specific option. - /// - T? GetOption(PerLanguageOption2 option, string? languageName); - - /// - /// Gets the current value of the specific option. - /// - object? GetOption(OptionKey optionKey); - - /// - /// Fetches an immutable set of all current options. - /// - SerializableOptionSet GetOptions(); - - /// - /// Gets a serializable option set snapshot with force computed values for all registered serializable options applicable for the given by quering the option persisters. - /// - SerializableOptionSet GetSerializableOptionsSnapshot(ImmutableHashSet languages); - - /// - /// Applies a set of options. - /// - /// New options to set. - void SetOptions(OptionSet optionSet); - - /// - /// Returns the set of all registered options. - /// - IEnumerable GetRegisteredOptions(); - - /// - bool TryMapEditorConfigKeyToOption(string key, string? language, [NotNullWhen(true)] out IEditorConfigStorageLocation2? storageLocation, out OptionKey optionKey); - - /// - /// Returns the set of all registered serializable options applicable for the given . - /// - ImmutableHashSet GetRegisteredSerializableOptions(ImmutableHashSet languages); - - /// - /// Registers a workspace with the option service. - /// - void RegisterWorkspace(Workspace workspace); - - /// - /// Unregisters a workspace from the option service. - /// - void UnregisterWorkspace(Workspace workspace); - } -} diff --git a/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs b/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs index 9162ab854c7e2..074db2ad944ad 100644 --- a/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs +++ b/src/Workspaces/Core/Portable/Options/OptionServiceFactory.cs @@ -4,56 +4,30 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Composition; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; -using Roslyn.Utilities; -namespace Microsoft.CodeAnalysis.Options +namespace Microsoft.CodeAnalysis.Options; + +[ExportWorkspaceService(typeof(ILegacyWorkspaceOptionService)), Shared] +internal sealed class LegacyWorkspaceOptionService : ILegacyWorkspaceOptionService { - [ExportWorkspaceServiceFactory(typeof(IOptionService)), Shared] - internal class OptionServiceFactory : IWorkspaceServiceFactory - { - private readonly IGlobalOptionService _globalOptionService; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public OptionServiceFactory(IGlobalOptionService globalOptionService) - => _globalOptionService = globalOptionService; - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - => new OptionService(_globalOptionService); - - internal sealed class OptionService : IOptionService - { - private readonly IGlobalOptionService _globalOptionService; - - public OptionService(IGlobalOptionService globalOptionService) - { - _globalOptionService = globalOptionService; - } - - // Simple forwarding functions. - public SerializableOptionSet GetOptions() => GetSerializableOptionsSnapshot(ImmutableHashSet.Empty); - public SerializableOptionSet GetSerializableOptionsSnapshot(ImmutableHashSet languages) => _globalOptionService.GetSerializableOptionsSnapshot(languages, this); - public object? GetOption(OptionKey optionKey) => _globalOptionService.GetOption(optionKey); - public T? GetOption(Option option) => _globalOptionService.GetOption(option); - public T? GetOption(Option2 option) => _globalOptionService.GetOption(option); - public T? GetOption(PerLanguageOption option, string? languageName) => _globalOptionService.GetOption(option, languageName); - public T? GetOption(PerLanguageOption2 option, string? languageName) => _globalOptionService.GetOption(option, languageName); - public IEnumerable GetRegisteredOptions() => _globalOptionService.GetRegisteredOptions(); - public bool TryMapEditorConfigKeyToOption(string key, string? language, [NotNullWhen(true)] out IEditorConfigStorageLocation2? storageLocation, out OptionKey optionKey) => _globalOptionService.TryMapEditorConfigKeyToOption(key, language, out storageLocation, out optionKey); - public ImmutableHashSet GetRegisteredSerializableOptions(ImmutableHashSet languages) => _globalOptionService.GetRegisteredSerializableOptions(languages); - public void SetOptions(OptionSet optionSet) => _globalOptionService.SetOptions(optionSet); - public void RegisterWorkspace(Workspace workspace) => _globalOptionService.RegisterWorkspace(workspace); - public void UnregisterWorkspace(Workspace workspace) => _globalOptionService.UnregisterWorkspace(workspace); - } - } + public IGlobalOptionService GlobalOptions { get; } + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public LegacyWorkspaceOptionService(IGlobalOptionService globalOptionService) + => GlobalOptions = globalOptionService; + + public void RegisterWorkspace(Workspace workspace) + => GlobalOptions.RegisterWorkspace(workspace); + + public void UnregisterWorkspace(Workspace workspace) + => GlobalOptions.UnregisterWorkspace(workspace); + + public object? GetOption(OptionKey key) + => GlobalOptions.GetOption(key); + + public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) + => GlobalOptions.SetOptions(optionSet, optionKeys); } diff --git a/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs b/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs index 6e52e3a5db884..f139251731c33 100644 --- a/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs +++ b/src/Workspaces/Core/Portable/Options/OptionSet+AnalyzerConfigOptionsImpl.cs @@ -16,10 +16,10 @@ public abstract partial class OptionSet private sealed class AnalyzerConfigOptionsImpl : AnalyzerConfigOptions { private readonly OptionSet _optionSet; - private readonly IOptionService _optionService; + private readonly ILegacyWorkspaceOptionService _optionService; private readonly string? _language; - public AnalyzerConfigOptionsImpl(OptionSet optionSet, IOptionService optionService, string? language) + public AnalyzerConfigOptionsImpl(OptionSet optionSet, ILegacyWorkspaceOptionService optionService, string? language) { _optionSet = optionSet; _optionService = optionService; @@ -28,7 +28,7 @@ public AnalyzerConfigOptionsImpl(OptionSet optionSet, IOptionService optionServi public override bool TryGetValue(string key, [NotNullWhen(true)] out string? value) { - if (!_optionService.TryMapEditorConfigKeyToOption(key, _language, out var storageLocation, out var optionKey)) + if (!_optionService.GlobalOptions.TryMapEditorConfigKeyToOption(key, _language, out var storageLocation, out var optionKey)) { // There are couple of reasons this assert might fire: // 1. Attempting to access an option which does not have an IEditorConfigStorageLocation. diff --git a/src/Workspaces/Core/Portable/Options/OptionSet.cs b/src/Workspaces/Core/Portable/Options/OptionSet.cs index d2e481a90e16a..9bdd26e705e82 100644 --- a/src/Workspaces/Core/Portable/Options/OptionSet.cs +++ b/src/Workspaces/Core/Portable/Options/OptionSet.cs @@ -112,18 +112,18 @@ public OptionSet WithChangedOption(PerLanguageOption option, string? langu internal OptionSet WithChangedOption(PerLanguageOption2 option, string? language, T value) => WithChangedOption(new OptionKey(option, language), value); - internal AnalyzerConfigOptions AsAnalyzerConfigOptions(IOptionService optionService, string? language) + internal AnalyzerConfigOptions AsAnalyzerConfigOptions(ILegacyWorkspaceOptionService optionService, string? language) { return ImmutableInterlocked.GetOrAdd( ref _lazyAnalyzerConfigOptions, language ?? NoLanguageSentinel, - (string language, (OptionSet self, IOptionService optionService) arg) => arg.self.CreateAnalyzerConfigOptions(arg.optionService, (object)language == NoLanguageSentinel ? null : language), + (string language, (OptionSet self, ILegacyWorkspaceOptionService optionService) arg) => arg.self.CreateAnalyzerConfigOptions(arg.optionService, (object)language == NoLanguageSentinel ? null : language), (this, optionService)); } internal abstract IEnumerable GetChangedOptions(OptionSet optionSet); - private protected virtual AnalyzerConfigOptions CreateAnalyzerConfigOptions(IOptionService optionService, string? language) + private protected virtual AnalyzerConfigOptions CreateAnalyzerConfigOptions(ILegacyWorkspaceOptionService optionService, string? language) => new AnalyzerConfigOptionsImpl(this, optionService, language); } } diff --git a/src/Workspaces/Core/Portable/Options/SerializableOptionSet.WorkspaceOptionSet.cs b/src/Workspaces/Core/Portable/Options/SerializableOptionSet.WorkspaceOptionSet.cs deleted file mode 100644 index 552451e5263c9..0000000000000 --- a/src/Workspaces/Core/Portable/Options/SerializableOptionSet.WorkspaceOptionSet.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using System.Collections.Immutable; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Options -{ - internal sealed partial class SerializableOptionSet : OptionSet - { - /// - /// An implementation of for non-serializable options that are defined in VS layers. - /// It fetches values it doesn't know about to the workspace's option service. It ensures a contract - /// that values are immutable from this instance once observed. - /// TODO: Remove this type once we move all the options from the VS layers into Workspaces/Features, so the entire - /// option set is serializable and becomes pure data snapshot for options. - /// - private sealed class WorkspaceOptionSet : OptionSet - { - private ImmutableDictionary _values; - - internal WorkspaceOptionSet(IOptionService service) - : this(service, ImmutableDictionary.Empty) - { - } - - public IOptionService OptionService { get; } - - private WorkspaceOptionSet(IOptionService service, ImmutableDictionary values) - { - OptionService = service; - _values = values; - } - - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)] - private protected override object? GetOptionCore(OptionKey optionKey) - { - if (_values.TryGetValue(optionKey, out var value)) - { - return value; - } - - value = OptionService.GetOption(optionKey); - return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value); - } - - public override OptionSet WithChangedOption(OptionKey optionAndLanguage, object? value) - { - // make sure we first load this in current optionset - this.GetOption(optionAndLanguage); - - return new WorkspaceOptionSet(OptionService, _values.SetItem(optionAndLanguage, value)); - } - - /// - /// Gets a list of all the options that were changed. - /// - internal IEnumerable GetChangedOptions() - { - var optionSet = OptionService.GetOptions(); - return GetChangedOptions(optionSet); - } - - internal override IEnumerable GetChangedOptions(OptionSet? optionSet) - { - if (optionSet == this) - { - yield break; - } - - foreach (var (key, value) in _values) - { - var currentValue = optionSet?.GetOption(key); - if (!object.Equals(currentValue, value)) - yield return key; - } - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Options/SerializableOptionSet.cs b/src/Workspaces/Core/Portable/Options/SerializableOptionSet.cs deleted file mode 100644 index 325cf9904e634..0000000000000 --- a/src/Workspaces/Core/Portable/Options/SerializableOptionSet.cs +++ /dev/null @@ -1,524 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using Microsoft.CodeAnalysis.CodeStyle; -using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; -using Microsoft.CodeAnalysis.PooledObjects; -using Microsoft.CodeAnalysis.Remote; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.Options -{ - /// - /// Serializable implementation of for . - /// It contains prepopulated fetched option values for all serializable options and values, and delegates to for non-serializable values. - /// It ensures a contract that values are immutable from this instance once observed. - /// - internal sealed partial class SerializableOptionSet : OptionSet - { - /// - /// Fallback option set for non-serializable options. See comments on for more details. - /// - private readonly WorkspaceOptionSet _workspaceOptionSet; - - /// - /// Prefetched option values applicable for . - /// - private readonly ImmutableDictionary _serializableOptionValues; - - /// - /// Set of changed options in this option set which are serializable. - /// - private readonly ImmutableHashSet _changedOptionKeysSerializable; - - /// - /// Set of changed options in this option set which are non-serializable. - /// - private readonly ImmutableHashSet _changedOptionKeysNonSerializable; - - /// - /// Set of languages referenced in . Cached - /// only so we can shortcircuit . - /// - private readonly Lazy> _languages; - - private SerializableOptionSet( - WorkspaceOptionSet workspaceOptionSet, - ImmutableDictionary values, - ImmutableHashSet changedOptionKeysSerializable, - ImmutableHashSet changedOptionKeysNonSerializable) - { - _workspaceOptionSet = workspaceOptionSet; - _serializableOptionValues = values; - _changedOptionKeysSerializable = changedOptionKeysSerializable; - _changedOptionKeysNonSerializable = changedOptionKeysNonSerializable; - - Debug.Assert(values.Keys.All(ShouldSerialize)); - Debug.Assert(changedOptionKeysSerializable.All(ShouldSerialize)); - Debug.Assert(changedOptionKeysNonSerializable.All(optionKey => !ShouldSerialize(optionKey))); - - _languages = new Lazy>(() => this.GetLanguagesAndValuesToSerialize(includeValues: false).languages); - } - - internal SerializableOptionSet( - IOptionService optionService, - ImmutableDictionary values, - ImmutableHashSet changedOptionKeysSerializable) - : this(new WorkspaceOptionSet(optionService), values, changedOptionKeysSerializable, changedOptionKeysNonSerializable: ImmutableHashSet.Empty) - { - } - - /// - /// Returns an option set with all the serializable option values prefetched for given , - /// while also retaining all the explicitly changed option values in this option set for any language. - /// Note: All the provided must be . - /// - public SerializableOptionSet UnionWithLanguages(ImmutableHashSet languages) - { - Debug.Assert(languages.All(RemoteSupportedLanguages.IsSupported)); - - if (_languages.Value.IsSupersetOf(languages)) - return this; - - // First create a base option set for the given languages. - languages = languages.Union(_languages.Value); - var newOptionSet = _workspaceOptionSet.OptionService.GetSerializableOptionsSnapshot(languages); - - // Then apply all the changed options from the current option set to the new option set. - foreach (var changedOption in this.GetChangedOptions()) - { - var valueInNewOptionSet = newOptionSet.GetOption(changedOption); - var changedValueInThisOptionSet = this.GetOption(changedOption); - - if (!Equals(changedValueInThisOptionSet, valueInNewOptionSet)) - { - newOptionSet = (SerializableOptionSet)newOptionSet.WithChangedOption(changedOption, changedValueInThisOptionSet); - } - } - - return newOptionSet; - } - - [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)] - private protected override object? GetOptionCore(OptionKey optionKey) - { - if (_serializableOptionValues.TryGetValue(optionKey, out var value)) - { - return value; - } - - return _workspaceOptionSet.GetOption(optionKey); - } - - private bool ShouldSerialize(OptionKey optionKey) - => _serializableOptionValues.ContainsKey(optionKey) && - (!optionKey.Option.IsPerLanguage || RemoteSupportedLanguages.IsSupported(optionKey.Language)); - - public override OptionSet WithChangedOption(OptionKey optionKey, object? value) - { - // Make sure we first load this in current optionset - var currentValue = this.GetOption(optionKey); - - // Check if the new value is the same as the current value. - if (Equals(value, currentValue)) - { - // Return a cloned option set as the public API 'WithChangedOption' guarantees a new option set is returned. - return new SerializableOptionSet( - _workspaceOptionSet, _serializableOptionValues, _changedOptionKeysSerializable, _changedOptionKeysNonSerializable); - } - - WorkspaceOptionSet workspaceOptionSet; - ImmutableDictionary serializableOptionValues; - ImmutableHashSet changedOptionKeysSerializable; - ImmutableHashSet changedOptionKeysNonSerializable; - if (ShouldSerialize(optionKey)) - { - workspaceOptionSet = _workspaceOptionSet; - serializableOptionValues = _serializableOptionValues.SetItem(optionKey, value); - changedOptionKeysSerializable = _changedOptionKeysSerializable.Add(optionKey); - changedOptionKeysNonSerializable = _changedOptionKeysNonSerializable; - } - else - { - workspaceOptionSet = (WorkspaceOptionSet)_workspaceOptionSet.WithChangedOption(optionKey, value); - serializableOptionValues = _serializableOptionValues; - changedOptionKeysSerializable = _changedOptionKeysSerializable; - changedOptionKeysNonSerializable = _changedOptionKeysNonSerializable.Add(optionKey); - } - - return new SerializableOptionSet( - workspaceOptionSet, serializableOptionValues, changedOptionKeysSerializable, changedOptionKeysNonSerializable); - } - - /// - /// Gets a list of all the options that were changed. - /// - internal IEnumerable GetChangedOptions() - => _changedOptionKeysSerializable.Concat(_changedOptionKeysNonSerializable); - - internal override IEnumerable GetChangedOptions(OptionSet? optionSet) - { - if (optionSet == this) - { - yield break; - } - - foreach (var key in GetChangedOptions()) - { - var currentValue = optionSet?.GetOption(key); - var changedValue = this.GetOption(key); - if (!object.Equals(currentValue, changedValue)) - { - yield return key; - } - } - } - - private (ImmutableHashSet languages, SortedDictionary values) GetLanguagesAndValuesToSerialize(bool includeValues) - { - var valuesBuilder = new SortedDictionary(OptionKeyComparer.Instance); - var languages = ImmutableHashSet.Empty; - - foreach (var (optionKey, value) in _serializableOptionValues) - { - Debug.Assert(ShouldSerialize(optionKey)); - - Debug.Assert(!optionKey.Option.IsPerLanguage || RemoteSupportedLanguages.IsSupported(optionKey.Language)); - if (optionKey.Language != null) - languages = languages.Add(optionKey.Language); - - if (includeValues) - { - OptionValueKind kind; - switch (value) - { - case ICodeStyleOption: - if (optionKey.Option.Type.GenericTypeArguments.Length != 1) - continue; - - kind = OptionValueKind.CodeStyleOption; - break; - - case NamingStylePreferences: - kind = OptionValueKind.NamingStylePreferences; - break; - - default: - kind = value != null && value.GetType().IsEnum ? OptionValueKind.Enum : OptionValueKind.Object; - break; - } - - valuesBuilder.Add(optionKey, (kind, value)); - } - } - - return (languages, valuesBuilder); - } - - public string GetDebugString() - { - // NOTE: keep this in sync with Serialize below. - - using var _ = PooledStringBuilder.GetInstance(out var sb); - - var (languages, values) = this.GetLanguagesAndValuesToSerialize(includeValues: true); - - sb.AppendLine($"languages count: {languages.Count}"); - foreach (var language in languages.Order()) - { - Debug.Assert(RemoteSupportedLanguages.IsSupported(language)); - sb.AppendLine(language); - } - - sb.AppendLine(); - sb.AppendLine($"values count: {values.Count}"); - foreach (var (optionKey, (kind, value)) in values) - { - SerializeOptionKey(optionKey); - - sb.Append($"{kind}: "); - if (kind == OptionValueKind.Enum) - { - RoslynDebug.Assert(value != null); - sb.AppendLine(value.ToString()); - } - else if (kind is OptionValueKind.CodeStyleOption) - { - RoslynDebug.Assert(value != null); - var codeStyleOption = (ICodeStyleOption)value; - sb.AppendLine(codeStyleOption.ToXElement().ToString()); - } - else if (kind is OptionValueKind.NamingStylePreferences) - { - RoslynDebug.Assert(value != null); - var namingStylePreferences = (NamingStylePreferences)value; - sb.AppendLine(namingStylePreferences.CreateXElement().ToString()); - } - else - { - sb.AppendLine($"{value}"); - } - - sb.AppendLine(); - } - - sb.AppendLine(); - sb.AppendLine($"changed options count: {_changedOptionKeysSerializable.Count}"); - foreach (var changedKey in _changedOptionKeysSerializable.OrderBy(OptionKeyComparer.Instance)) - SerializeOptionKey(changedKey); - - return sb.ToString(); - - void SerializeOptionKey(OptionKey optionKey) - { - Debug.Assert(ShouldSerialize(optionKey)); - - sb.AppendLine($"{optionKey.Option.Name} {optionKey.Option.Feature} {optionKey.Option.IsPerLanguage} {optionKey.Language}"); - } - } - - public void Serialize(ObjectWriter writer, CancellationToken cancellationToken) - { - // We serialize the following contents from this option set: - // 1. Languages - // 2. Prefetched serializable option key-value pairs - // 3. Changed option keys. - - // NOTE: keep the serialization in sync with Deserialize method below. - // NOTE: keep this in sync with GetDebugString above. - - cancellationToken.ThrowIfCancellationRequested(); - - var (languages, values) = this.GetLanguagesAndValuesToSerialize(includeValues: true); - - writer.WriteInt32(languages.Count); - foreach (var language in languages.Order()) - { - Debug.Assert(RemoteSupportedLanguages.IsSupported(language)); - writer.WriteString(language); - } - - writer.WriteInt32(values.Count); - foreach (var (optionKey, (kind, value)) in values) - { - SerializeOptionKey(optionKey); - - writer.WriteInt32((int)kind); - if (kind == OptionValueKind.Enum) - { - RoslynDebug.Assert(value != null); - writer.WriteInt32((int)value); - } - else if (kind is OptionValueKind.CodeStyleOption or OptionValueKind.NamingStylePreferences) - { - RoslynDebug.Assert(value != null); - ((IObjectWritable)value).WriteTo(writer); - } - else - { - writer.WriteValue(value); - } - } - - writer.WriteInt32(_changedOptionKeysSerializable.Count); - foreach (var changedKey in _changedOptionKeysSerializable.OrderBy(OptionKeyComparer.Instance)) - SerializeOptionKey(changedKey); - - return; - - void SerializeOptionKey(OptionKey optionKey) - { - Debug.Assert(ShouldSerialize(optionKey)); - - writer.WriteString(optionKey.Option.Name); - writer.WriteString(optionKey.Option.Feature); - writer.WriteBoolean(optionKey.Option.IsPerLanguage); - if (optionKey.Option.IsPerLanguage) - { - writer.WriteString(optionKey.Language); - } - } - } - - public static SerializableOptionSet Deserialize(ObjectReader reader, IOptionService optionService, CancellationToken cancellationToken) - { - // We deserialize the following contents from this option set: - // 1. Languages - // 2. Prefetched serializable option key-value pairs - // 3. Changed option keys. - - // NOTE: keep the deserialization in sync with Serialize method above. - - cancellationToken.ThrowIfCancellationRequested(); - - var count = reader.ReadInt32(); - var languagesBuilder = ImmutableHashSet.CreateBuilder(); - for (var i = 0; i < count; i++) - { - var language = reader.ReadString(); - Debug.Assert(RemoteSupportedLanguages.IsSupported(language)); - languagesBuilder.Add(language); - } - - var languages = languagesBuilder.ToImmutable(); - - var serializableOptions = optionService.GetRegisteredSerializableOptions(languages); - var lookup = serializableOptions.ToLookup(o => o.Name); - - count = reader.ReadInt32(); - var builder = ImmutableDictionary.CreateBuilder(); - for (var i = 0; i < count; i++) - { - var optionKeyOpt = TryDeserializeOptionKey(reader, lookup); - - var kind = (OptionValueKind)reader.ReadInt32(); - var readValue = kind switch - { - OptionValueKind.Enum => reader.ReadInt32(), - OptionValueKind.CodeStyleOption => CodeStyleOption2.ReadFrom(reader), - OptionValueKind.NamingStylePreferences => NamingStylePreferences.ReadFrom(reader), - _ => reader.ReadValue(), - }; - - if (optionKeyOpt == null) - continue; - - var optionKey = optionKeyOpt.Value; - if (!serializableOptions.Contains(optionKey.Option)) - continue; - - object? optionValue; - switch (kind) - { - case OptionValueKind.CodeStyleOption: - if (optionKey.Option.DefaultValue is not ICodeStyleOption defaultValue || - optionKey.Option.Type.GenericTypeArguments.Length != 1) - { - continue; - } - - var parsedCodeStyleOption = (CodeStyleOption2)readValue; - var value = parsedCodeStyleOption.Value; - var type = optionKey.Option.Type.GenericTypeArguments[0]; - var convertedValue = type.IsEnum ? Enum.ToObject(type, value) : Convert.ChangeType(value, type); - optionValue = defaultValue.WithValue(convertedValue).WithNotification(parsedCodeStyleOption.Notification); - break; - - case OptionValueKind.NamingStylePreferences: - optionValue = (NamingStylePreferences)readValue; - break; - - case OptionValueKind.Enum: - var enumType = optionKey.Option.Type; - if (enumType.IsGenericType && enumType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - enumType = enumType.GetGenericArguments()[0]; - } - - optionValue = Enum.ToObject(enumType, readValue); - break; - - default: - optionValue = readValue; - break; - } - - builder[optionKey] = optionValue; - } - - count = reader.ReadInt32(); - var changedKeysBuilder = ImmutableHashSet.CreateBuilder(); - for (var i = 0; i < count; i++) - { - if (TryDeserializeOptionKey(reader, lookup) is { } optionKey) - changedKeysBuilder.Add(optionKey); - } - - var serializableOptionValues = builder.ToImmutable(); - var changedOptionKeysSerializable = changedKeysBuilder.ToImmutable(); - var workspaceOptionSet = new WorkspaceOptionSet(optionService); - - return new SerializableOptionSet( - workspaceOptionSet, serializableOptionValues, changedOptionKeysSerializable, - changedOptionKeysNonSerializable: ImmutableHashSet.Empty); - - static OptionKey? TryDeserializeOptionKey(ObjectReader reader, ILookup lookup) - { - var name = reader.ReadString(); - var feature = reader.ReadString(); - var isPerLanguage = reader.ReadBoolean(); - var language = isPerLanguage ? reader.ReadString() : null; - - foreach (var option in lookup[name]) - { - if (option.Feature == feature && - option.IsPerLanguage == isPerLanguage) - { - return new OptionKey(option, language); - } - } - - Debug.Fail($"Failed to deserialize: {name}-{feature}-{isPerLanguage}-{language}"); - return null; - } - } - - public TestAccessor GetTestAccessor() - => new(this); - - public struct TestAccessor - { - private readonly SerializableOptionSet _serializableOptionSet; - - public TestAccessor(SerializableOptionSet serializableOptionSet) - { - _serializableOptionSet = serializableOptionSet; - } - - public ImmutableHashSet Languages - => _serializableOptionSet.GetLanguagesAndValuesToSerialize(includeValues: true).languages; - } - - private enum OptionValueKind - { - CodeStyleOption, - NamingStylePreferences, - Object, - Enum - } - - private sealed class OptionKeyComparer : IComparer - { - public static readonly OptionKeyComparer Instance = new(); - private OptionKeyComparer() { } - - public int Compare(OptionKey x, OptionKey y) - { - if (x.Option.Name != y.Option.Name) - { - return StringComparer.Ordinal.Compare(x.Option.Name, y.Option.Name); - } - - if (x.Option.Feature != y.Option.Feature) - { - return StringComparer.Ordinal.Compare(x.Option.Feature, y.Option.Feature); - } - - if (x.Language != y.Language) - { - return StringComparer.Ordinal.Compare(x.Language, y.Language); - } - - return Comparer.Default.Compare(x.GetHashCode(), y.GetHashCode()); - } - } - } -} diff --git a/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs b/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs new file mode 100644 index 0000000000000..78c461b8b7ca8 --- /dev/null +++ b/src/Workspaces/Core/Portable/Options/SolutionOptionSet.cs @@ -0,0 +1,108 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Remote; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Options +{ + /// + /// Implements in-proc only storage for . + /// Supports tracking changed options. + /// Options that are not set in the option set are read from global options and cached. + /// + internal sealed class SolutionOptionSet : OptionSet + { + private readonly ILegacyWorkspaceOptionService _globalOptions; + + /// + /// Cached values read from global options. + /// + private ImmutableDictionary _values; + + /// + /// Keys of options whose current value stored in differs from the value originally read from global options. + /// + private readonly ImmutableHashSet _changedOptionKeys; + + private SolutionOptionSet( + ILegacyWorkspaceOptionService globalOptions, + ImmutableDictionary values, + ImmutableHashSet changedOptionKeys) + { + _globalOptions = globalOptions; + _values = values; + _changedOptionKeys = changedOptionKeys; + } + + internal SolutionOptionSet(ILegacyWorkspaceOptionService globalOptions) + : this(globalOptions, values: ImmutableDictionary.Empty, changedOptionKeys: ImmutableHashSet.Empty) + { + } + + [PerformanceSensitive("https://github.com/dotnet/roslyn/issues/30819", AllowLocks = false)] + private protected override object? GetOptionCore(OptionKey optionKey) + { + if (_values.TryGetValue(optionKey, out var value)) + { + return value is ICodeStyleOption codeStyleOption ? codeStyleOption.AsPublicCodeStyleOption() : value; + } + + value = _globalOptions.GetOption(optionKey); + return ImmutableInterlocked.GetOrAdd(ref _values, optionKey, value); + } + + public override OptionSet WithChangedOption(OptionKey optionKey, object? value) + { + // Make sure we first load this in current optionset + var currentValue = GetOption(optionKey); + + // Check if the new value is the same as the current value. + if (Equals(value, currentValue)) + { + // Return a cloned option set as the public API 'WithChangedOption' guarantees a new option set is returned. + return new SolutionOptionSet(_globalOptions, _values, _changedOptionKeys); + } + + return new SolutionOptionSet( + _globalOptions, + _values.SetItem(optionKey, value), + _changedOptionKeys.Add(optionKey)); + } + + /// + /// Gets a list of all the options that were changed. + /// + internal IEnumerable GetChangedOptions() + => _changedOptionKeys; + + internal override IEnumerable GetChangedOptions(OptionSet? optionSet) + { + if (optionSet == this) + { + yield break; + } + + foreach (var key in GetChangedOptions()) + { + var currentValue = optionSet?.GetOption(key); + var changedValue = this.GetOption(key); + if (!object.Equals(currentValue, changedValue)) + { + yield return key; + } + } + } + } +} diff --git a/src/Workspaces/Core/Portable/Simplification/Simplifier.cs b/src/Workspaces/Core/Portable/Simplification/Simplifier.cs index 21030d131fa7a..52b1cddc5a16a 100644 --- a/src/Workspaces/Core/Portable/Simplification/Simplifier.cs +++ b/src/Workspaces/Core/Portable/Simplification/Simplifier.cs @@ -242,7 +242,7 @@ internal static async Task ReduceAsync( internal static async Task GetOptionsAsync(Document document, OptionSet? optionSet, CancellationToken cancellationToken) { var services = document.Project.Solution.Workspace.Services; - var optionService = services.GetRequiredService(); + var optionService = services.GetRequiredService(); var configOptionSet = (optionSet ?? await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false)).AsAnalyzerConfigOptions(optionService, document.Project.Language); var simplificationService = services.GetRequiredLanguageService(document.Project.Language); return simplificationService.GetSimplifierOptions(configOptionSet, fallbackOptions: null); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs index 36858b84eb0fe..f150f302f9e2b 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs @@ -37,7 +37,7 @@ private Solution(SolutionState state) _state = state; } - internal Solution(Workspace workspace, SolutionInfo.SolutionAttributes solutionAttributes, SerializableOptionSet options, IReadOnlyList analyzerReferences) + internal Solution(Workspace workspace, SolutionInfo.SolutionAttributes solutionAttributes, SolutionOptionSet options, IReadOnlyList analyzerReferences) : this(new SolutionState(workspace.PrimaryBranchId, new SolutionServices(workspace), solutionAttributes, options, analyzerReferences)) { } @@ -1816,7 +1816,7 @@ public Solution WithOptions(OptionSet options) { return options switch { - SerializableOptionSet serializableOptions => WithOptions(serializableOptions), + SolutionOptionSet serializableOptions => WithOptions(serializableOptions), null => throw new ArgumentNullException(nameof(options)), _ => throw new ArgumentException(WorkspacesResources.Options_did_not_come_from_specified_Solution, paramName: nameof(options)) }; @@ -1825,7 +1825,7 @@ public Solution WithOptions(OptionSet options) /// /// Creates a new solution instance with the specified serializable . /// - internal Solution WithOptions(SerializableOptionSet options) + internal Solution WithOptions(SolutionOptionSet options) { var newState = _state.WithOptions(options: options); if (newState == _state) diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionInfo.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionInfo.cs index f865a6bf124ad..4119d66a3f197 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionInfo.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionInfo.cs @@ -87,20 +87,6 @@ public static SolutionInfo Create( PublicContract.ToBoxedImmutableArrayWithDistinctNonNullItems(analyzerReferences, nameof(analyzerReferences))); } - internal ImmutableHashSet GetRemoteSupportedProjectLanguages() - { - var builder = ImmutableHashSet.CreateBuilder(); - foreach (var project in Projects) - { - if (RemoteSupportedLanguages.IsSupported(project.Language)) - { - builder.Add(project.Language); - } - } - - return builder.ToImmutable(); - } - internal SolutionInfo WithTelemetryId(Guid telemetryId) => new(Attributes.With(telemetryId: telemetryId), Projects, AnalyzerReferences); diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs index 2af7194833331..6c1497227b973 100644 --- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs +++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs @@ -42,7 +42,6 @@ internal partial class SolutionState private readonly SolutionInfo.SolutionAttributes _solutionAttributes; private readonly SolutionServices _solutionServices; private readonly ImmutableDictionary _projectIdToProjectStateMap; - private readonly ImmutableHashSet _remoteSupportedLanguages; private readonly ImmutableDictionary> _filePathToDocumentIdsMap; private readonly ProjectDependencyGraph _dependencyGraph; @@ -79,10 +78,9 @@ private SolutionState( SolutionServices solutionServices, SolutionInfo.SolutionAttributes solutionAttributes, IReadOnlyList projectIds, - SerializableOptionSet options, + SolutionOptionSet options, IReadOnlyList analyzerReferences, ImmutableDictionary idToProjectStateMap, - ImmutableHashSet remoteSupportedLanguages, ImmutableDictionary projectIdToTrackerMap, ImmutableDictionary> filePathToDocumentIdsMap, ProjectDependencyGraph dependencyGraph, @@ -97,7 +95,6 @@ private SolutionState( Options = options; AnalyzerReferences = analyzerReferences; _projectIdToProjectStateMap = idToProjectStateMap; - _remoteSupportedLanguages = remoteSupportedLanguages; _projectIdToTrackerMap = projectIdToTrackerMap; _filePathToDocumentIdsMap = filePathToDocumentIdsMap; _dependencyGraph = dependencyGraph; @@ -119,7 +116,7 @@ public SolutionState( BranchId primaryBranchId, SolutionServices solutionServices, SolutionInfo.SolutionAttributes solutionAttributes, - SerializableOptionSet options, + SolutionOptionSet options, IReadOnlyList analyzerReferences) : this( primaryBranchId, @@ -130,7 +127,6 @@ public SolutionState( options, analyzerReferences, idToProjectStateMap: ImmutableDictionary.Empty, - remoteSupportedLanguages: ImmutableHashSet.Empty, projectIdToTrackerMap: ImmutableDictionary.Empty, filePathToDocumentIdsMap: ImmutableDictionary.Create>(StringComparer.OrdinalIgnoreCase), dependencyGraph: ProjectDependencyGraph.Empty, @@ -162,7 +158,7 @@ public SolutionState WithNewWorkspace(Workspace workspace, int workspaceVersion) public SolutionServices Services => _solutionServices; - public SerializableOptionSet Options { get; } + public SolutionOptionSet Options { get; } /// /// branch id of this solution @@ -209,28 +205,22 @@ private void CheckInvariants() Contract.ThrowIfFalse(_projectIdToProjectStateMap.Count == _dependencyGraph.ProjectIds.Count); // Only run this in debug builds; even the .Any() call across all projects can be expensive when there's a lot of them. - #if DEBUG - // An id shouldn't point at a tracker for a different project. Contract.ThrowIfTrue(_projectIdToTrackerMap.Any(kvp => kvp.Key != kvp.Value.ProjectState.Id)); // project ids must be the same: Debug.Assert(_projectIdToProjectStateMap.Keys.SetEquals(ProjectIds)); Debug.Assert(_projectIdToProjectStateMap.Keys.SetEquals(_dependencyGraph.ProjectIds)); - - Debug.Assert(_remoteSupportedLanguages.SetEquals(GetRemoteSupportedProjectLanguages(_projectIdToProjectStateMap))); - #endif } private SolutionState Branch( SolutionInfo.SolutionAttributes? solutionAttributes = null, IReadOnlyList? projectIds = null, - SerializableOptionSet? options = null, + SolutionOptionSet? options = null, IReadOnlyList? analyzerReferences = null, ImmutableDictionary? idToProjectStateMap = null, - ImmutableHashSet? remoteSupportedProjectLanguages = null, ImmutableDictionary? projectIdToTrackerMap = null, ImmutableDictionary>? filePathToDocumentIdsMap = null, ProjectDependencyGraph? dependencyGraph = null, @@ -238,17 +228,10 @@ private SolutionState Branch( { var branchId = GetBranchId(); - if (idToProjectStateMap is not null) - { - Contract.ThrowIfNull(remoteSupportedProjectLanguages); - } - solutionAttributes ??= _solutionAttributes; projectIds ??= ProjectIds; idToProjectStateMap ??= _projectIdToProjectStateMap; - remoteSupportedProjectLanguages ??= _remoteSupportedLanguages; - Debug.Assert(remoteSupportedProjectLanguages.SetEquals(GetRemoteSupportedProjectLanguages(idToProjectStateMap))); - options ??= Options.UnionWithLanguages(remoteSupportedProjectLanguages); + options ??= Options; analyzerReferences ??= AnalyzerReferences; projectIdToTrackerMap ??= _projectIdToTrackerMap; filePathToDocumentIdsMap ??= _filePathToDocumentIdsMap; @@ -280,7 +263,6 @@ private SolutionState Branch( options, analyzerReferences, idToProjectStateMap, - remoteSupportedProjectLanguages, projectIdToTrackerMap, filePathToDocumentIdsMap, dependencyGraph, @@ -309,7 +291,6 @@ private SolutionState CreatePrimarySolution( Options, AnalyzerReferences, _projectIdToProjectStateMap, - _remoteSupportedLanguages, _projectIdToTrackerMap, _filePathToDocumentIdsMap, _dependencyGraph, @@ -493,9 +474,6 @@ private SolutionState AddProject(ProjectId projectId, ProjectState projectState) var newProjectIds = ProjectIds.ToImmutableArray().Add(projectId); var newStateMap = _projectIdToProjectStateMap.Add(projectId, projectState); - var newLanguages = RemoteSupportedLanguages.IsSupported(projectState.Language) - ? _remoteSupportedLanguages.Add(projectState.Language) - : _remoteSupportedLanguages; var newDependencyGraph = _dependencyGraph .WithAdditionalProject(projectId) @@ -525,7 +503,6 @@ private SolutionState AddProject(ProjectId projectId, ProjectState projectState) solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, - remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap, filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); @@ -609,31 +586,6 @@ public SolutionState RemoveProject(ProjectId projectId) var newProjectIds = ProjectIds.ToImmutableArray().Remove(projectId); var newStateMap = _projectIdToProjectStateMap.Remove(projectId); - - // Remote supported languages only changes if the removed project is the last project of a supported language. - var newLanguages = _remoteSupportedLanguages; - if (_projectIdToProjectStateMap.TryGetValue(projectId, out var projectState) - && RemoteSupportedLanguages.IsSupported(projectState.Language)) - { - var stillSupportsLanguage = false; - foreach (var (id, state) in _projectIdToProjectStateMap) - { - if (id == projectId) - continue; - - if (state.Language == projectState.Language) - { - stillSupportsLanguage = true; - break; - } - } - - if (!stillSupportsLanguage) - { - newLanguages = newLanguages.Remove(projectState.Language); - } - } - var newDependencyGraph = _dependencyGraph.WithProjectRemoved(projectId); var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); var newFilePathToDocumentIdsMap = CreateFilePathToDocumentIdsMapWithRemovedDocuments(GetDocumentStates(_projectIdToProjectStateMap[projectId])); @@ -642,7 +594,6 @@ public SolutionState RemoveProject(ProjectId projectId) solutionAttributes: newSolutionAttributes, projectIds: newProjectIds, idToProjectStateMap: newStateMap, - remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap.Remove(projectId), filePathToDocumentIdsMap: newFilePathToDocumentIdsMap, dependencyGraph: newDependencyGraph); @@ -1541,12 +1492,6 @@ private SolutionState ForkProject( Contract.ThrowIfFalse(_projectIdToProjectStateMap.ContainsKey(projectId)); var newStateMap = _projectIdToProjectStateMap.SetItem(projectId, newProjectState); - // Remote supported languages can only change if the project changes language. This is an unexpected edge - // case, so it's not optimized for incremental updates. - var newLanguages = !_projectIdToProjectStateMap.TryGetValue(projectId, out var projectState) || projectState.Language != newProjectState.Language - ? GetRemoteSupportedProjectLanguages(newStateMap) - : _remoteSupportedLanguages; - newDependencyGraph ??= _dependencyGraph; var newTrackerMap = CreateCompilationTrackerMap(projectId, newDependencyGraph); // If we have a tracker for this project, then fork it as well (along with the @@ -1563,7 +1508,6 @@ private SolutionState ForkProject( return this.Branch( idToProjectStateMap: newStateMap, - remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newTrackerMap, dependencyGraph: newDependencyGraph, filePathToDocumentIdsMap: newFilePathToDocumentIdsMap ?? _filePathToDocumentIdsMap); @@ -1618,7 +1562,7 @@ bool CanReuse(ProjectId id) } } - public SolutionState WithOptions(SerializableOptionSet options) + public SolutionState WithOptions(SolutionOptionSet options) => Branch(options: options); public SolutionState AddAnalyzerReferences(IReadOnlyCollection analyzerReferences) @@ -1716,12 +1660,10 @@ public SolutionState WithFrozenPartialCompilationIncludingSpecificDocument(Docum Contract.ThrowIfFalse(_projectIdToProjectStateMap.ContainsKey(documentId.ProjectId)); var newIdToProjectStateMap = _projectIdToProjectStateMap.SetItem(documentId.ProjectId, newTracker.ProjectState); - var newLanguages = _remoteSupportedLanguages; var newIdToTrackerMap = _projectIdToTrackerMap.SetItem(documentId.ProjectId, newTracker); currentPartialSolution = this.Branch( idToProjectStateMap: newIdToProjectStateMap, - remoteSupportedProjectLanguages: newLanguages, projectIdToTrackerMap: newIdToTrackerMap, dependencyGraph: CreateDependencyGraph(ProjectIds, newIdToProjectStateMap)); @@ -2037,23 +1979,6 @@ internal bool ContainsAnalyzerReference(ProjectId projectId, AnalyzerReference a internal bool ContainsTransitiveReference(ProjectId fromProjectId, ProjectId toProjectId) => _dependencyGraph.GetProjectsThatThisProjectTransitivelyDependsOn(fromProjectId).Contains(toProjectId); - internal ImmutableHashSet GetRemoteSupportedProjectLanguages() - => _remoteSupportedLanguages; - - private static ImmutableHashSet GetRemoteSupportedProjectLanguages(ImmutableDictionary projectStates) - { - var builder = ImmutableHashSet.CreateBuilder(); - foreach (var projectState in projectStates) - { - if (RemoteSupportedLanguages.IsSupported(projectState.Value.Language)) - { - builder.Add(projectState.Value.Language); - } - } - - return builder.ToImmutable(); - } - internal TestAccessor GetTestAccessor() => new TestAccessor(this); internal readonly struct TestAccessor diff --git a/src/Workspaces/Core/Portable/Workspace/Workspace.cs b/src/Workspaces/Core/Portable/Workspace/Workspace.cs index f97dd477a9e17..8fd316e7f0aaf 100644 --- a/src/Workspaces/Core/Portable/Workspace/Workspace.cs +++ b/src/Workspaces/Core/Portable/Workspace/Workspace.cs @@ -37,7 +37,7 @@ public abstract partial class Workspace : IDisposable private readonly HostWorkspaceServices _services; private readonly BranchId _primaryBranchId; - private readonly IOptionService _optionService; + private readonly ILegacyWorkspaceOptionService _legacyOptions; // forces serialization of mutation calls from host (OnXXX methods). Must take this lock before taking stateLock. private readonly SemaphoreSlim _serializationLock = new(initialCount: 1); @@ -76,8 +76,8 @@ protected Workspace(HostServices host, string? workspaceKind) _services = host.CreateWorkspaceServices(this); - _optionService = _services.GetRequiredService(); - _optionService.RegisterWorkspace(this); + _legacyOptions = _services.GetRequiredService(); + _legacyOptions.RegisterWorkspace(this); // queue used for sending events var schedulerProvider = _services.GetRequiredService(); @@ -87,8 +87,7 @@ protected Workspace(HostServices host, string? workspaceKind) // initialize with empty solution var info = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Create()); - var emptyOptions = new SerializableOptionSet( - _optionService, ImmutableDictionary.Empty, changedOptionKeysSerializable: ImmutableHashSet.Empty); + var emptyOptions = new SolutionOptionSet(_legacyOptions); _latestSolution = CreateSolution(info, emptyOptions, analyzerReferences: SpecializedCollections.EmptyReadOnlyList()); } @@ -131,14 +130,14 @@ internal void SetTestLogger(Action? writeLineMessageLogger) /// protected internal Solution CreateSolution(SolutionInfo solutionInfo) { - var options = _optionService.GetSerializableOptionsSnapshot(solutionInfo.GetRemoteSupportedProjectLanguages()); + var options = new SolutionOptionSet(_legacyOptions); return CreateSolution(solutionInfo, options, solutionInfo.AnalyzerReferences); } /// /// Create a new empty solution instance associated with this workspace, and with the given options. /// - private Solution CreateSolution(SolutionInfo solutionInfo, SerializableOptionSet options, IReadOnlyList analyzerReferences) + private Solution CreateSolution(SolutionInfo solutionInfo, SolutionOptionSet options, IReadOnlyList analyzerReferences) => new(this, solutionInfo.Attributes, options, analyzerReferences); /// @@ -255,21 +254,20 @@ public OptionSet Options [Obsolete(@"Workspace options should be set by invoking 'workspace.TryApplyChanges(workspace.CurrentSolution.WithOptions(newOptionSet))'")] set { - SetOptions(value); + var changedOptionKeys = value switch + { + null => throw new ArgumentNullException(nameof(value)), + SolutionOptionSet serializableOptionSet => serializableOptionSet.GetChangedOptions(), + _ => throw new ArgumentException(WorkspacesResources.Options_did_not_come_from_specified_Solution, paramName: nameof(value)) + }; + + _legacyOptions.SetOptions(value, changedOptionKeys); } } - /// - /// Sets global options and to have the new options. - /// NOTE: This method also updates to a new solution instance with updated . - /// - internal void SetOptions(OptionSet options) - => _optionService.SetOptions(options); - internal void UpdateCurrentSolutionOnOptionsChanged() { - var newOptions = _optionService.GetSerializableOptionsSnapshot(this.CurrentSolution.State.GetRemoteSupportedProjectLanguages()); - this.SetCurrentSolution(this.CurrentSolution.WithOptions(newOptions)); + SetCurrentSolution(CurrentSolution.WithOptions(new SolutionOptionSet(_legacyOptions))); } /// @@ -369,7 +367,7 @@ protected virtual void Dispose(bool finalize) this.Services.GetService()?.Stop(); } - _optionService.UnregisterWorkspace(this); + _legacyOptions.UnregisterWorkspace(this); // Directly dispose IRemoteHostClientProvider if necessary. This is a test hook to ensure RemoteWorkspace // gets disposed in unit tests as soon as TestWorkspace gets disposed. This would be superseded by direct @@ -1247,7 +1245,7 @@ internal virtual bool TryApplyChanges(Solution newSolution, IProgressTracker pro if (this.CurrentSolution.Options != newSolution.Options) { - SetOptions(newSolution.Options); + _legacyOptions.SetOptions(newSolution.State.Options, newSolution.State.Options.GetChangedOptions()); } if (!CurrentSolution.AnalyzerReferences.SequenceEqual(newSolution.AnalyzerReferences)) diff --git a/src/Workspaces/CoreTest/Formatter/FormatterTests.cs b/src/Workspaces/CoreTest/Formatter/FormatterTests.cs index d1c1acd3360cc..4116352cfb556 100644 --- a/src/Workspaces/CoreTest/Formatter/FormatterTests.cs +++ b/src/Workspaces/CoreTest/Formatter/FormatterTests.cs @@ -118,16 +118,16 @@ public async Task PublicOptions() var csDocument = workspace.AddDocument(csProject.Id, "File.cs", SourceText.From("class C { }")); var vbDocument = workspace.AddDocument(vbProject.Id, "File.vb", SourceText.From("Class C : End Class")); - var updatedOptions = GetOptionSetWithChangedPublicOptions(workspace.CurrentSolution.Options); - // Validate that options are read from specified OptionSet: + var updatedOptions = OptionsTestHelpers.GetOptionSetWithChangedOptions(OptionValueSet.Empty, OptionsTestHelpers.PublicFormattingOptionsWithNonDefaultValues); ValidateCSharpOptions((CSharpSyntaxFormattingOptions)(await Formatter.GetFormattingOptionsAsync(csDocument, updatedOptions, CancellationToken.None)).Syntax!); ValidateVisualBasicOptions((VisualBasicSyntaxFormattingOptions)(await Formatter.GetFormattingOptionsAsync(vbDocument, updatedOptions, CancellationToken.None)).Syntax!); // Validate that options are read from solution snapshot as a fallback (we have no editorconfig file, so all options should fall back): - var solutionWithUpdatedOptions = workspace.CurrentSolution.WithOptions(updatedOptions); + var updatedSolutionOptions = OptionsTestHelpers.GetOptionSetWithChangedOptions(workspace.CurrentSolution.Options, OptionsTestHelpers.PublicFormattingOptionsWithNonDefaultValues); + var solutionWithUpdatedOptions = workspace.CurrentSolution.WithOptions(updatedSolutionOptions); var csDocumentWithUpdatedOptions = solutionWithUpdatedOptions.GetRequiredDocument(csDocument.Id); var vbDocumentWithUpdatedOptions = solutionWithUpdatedOptions.GetRequiredDocument(vbDocument.Id); @@ -136,79 +136,6 @@ public async Task PublicOptions() ValidateOrganizeImportsOptions(await Formatter.GetOrganizeImportsOptionsAsync(csDocumentWithUpdatedOptions, CancellationToken.None)); ValidateOrganizeImportsOptions(await Formatter.GetOrganizeImportsOptionsAsync(vbDocumentWithUpdatedOptions, CancellationToken.None)); - static OptionSet GetOptionSetWithChangedPublicOptions(OptionSet options) - { - // all public options and their non-default values: - - var publicOptions = new (IOption, object)[] - { - (FormattingOptions.UseTabs, true), - (FormattingOptions.TabSize, 5), - (FormattingOptions.IndentationSize, 7), - (FormattingOptions.NewLine, "\r"), - (CSharpFormattingOptions.IndentBlock, false), - (CSharpFormattingOptions.IndentBraces, true), - (CSharpFormattingOptions.IndentSwitchCaseSection, false), - (CSharpFormattingOptions.IndentSwitchCaseSectionWhenBlock, false), - (CSharpFormattingOptions.IndentSwitchSection, false), - (CSharpFormattingOptions.LabelPositioning, LabelPositionOptions.LeftMost), - (CSharpFormattingOptions.NewLineForCatch, false), - (CSharpFormattingOptions.NewLineForClausesInQuery, false), - (CSharpFormattingOptions.NewLineForElse, false), - (CSharpFormattingOptions.NewLineForFinally, false), - (CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, false), - (CSharpFormattingOptions.NewLineForMembersInObjectInit, false), - (CSharpFormattingOptions.NewLinesForBracesInAccessors, false), - (CSharpFormattingOptions.NewLinesForBracesInAnonymousMethods, false), - (CSharpFormattingOptions.NewLinesForBracesInAnonymousTypes, false), - (CSharpFormattingOptions.NewLinesForBracesInControlBlocks, false), - (CSharpFormattingOptions.NewLinesForBracesInLambdaExpressionBody, false), - (CSharpFormattingOptions.NewLinesForBracesInMethods, false), - (CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers, false), - (CSharpFormattingOptions.NewLinesForBracesInProperties, false), - (CSharpFormattingOptions.NewLinesForBracesInTypes, false), - (CSharpFormattingOptions.SpaceAfterCast, true), - (CSharpFormattingOptions.SpaceAfterColonInBaseTypeDeclaration, false), - (CSharpFormattingOptions.SpaceAfterComma, false), - (CSharpFormattingOptions.SpaceAfterControlFlowStatementKeyword, false), - (CSharpFormattingOptions.SpaceAfterDot, true), - (CSharpFormattingOptions.SpaceAfterMethodCallName, true), - (CSharpFormattingOptions.SpaceAfterSemicolonsInForStatement, false), - (CSharpFormattingOptions.SpaceBeforeColonInBaseTypeDeclaration, false), - (CSharpFormattingOptions.SpaceBeforeComma, true), - (CSharpFormattingOptions.SpaceBeforeDot, true), - (CSharpFormattingOptions.SpaceBeforeOpenSquareBracket, true), - (CSharpFormattingOptions.SpaceBeforeSemicolonsInForStatement, true), - (CSharpFormattingOptions.SpaceBetweenEmptyMethodCallParentheses, true), - (CSharpFormattingOptions.SpaceBetweenEmptyMethodDeclarationParentheses, true), - (CSharpFormattingOptions.SpaceBetweenEmptySquareBrackets, true), - (CSharpFormattingOptions.SpacesIgnoreAroundVariableDeclaration, true), - (CSharpFormattingOptions.SpaceWithinCastParentheses, true), - (CSharpFormattingOptions.SpaceWithinExpressionParentheses, true), - (CSharpFormattingOptions.SpaceWithinMethodCallParentheses, true), - (CSharpFormattingOptions.SpaceWithinMethodDeclarationParenthesis, true), - (CSharpFormattingOptions.SpaceWithinOtherParentheses, true), - (CSharpFormattingOptions.SpaceWithinSquareBrackets, true), - (CSharpFormattingOptions.SpacingAfterMethodDeclarationName, true), - (CSharpFormattingOptions.SpacingAroundBinaryOperator, BinaryOperatorSpacingOptions.Remove), - (CSharpFormattingOptions.WrappingKeepStatementsOnSingleLine, false), - (CSharpFormattingOptions.WrappingPreserveSingleLine, false), - }; - - var updatedOptions = options; - foreach (var (option, newValue) in publicOptions) - { - var languages = (option is IPerLanguageOption) ? new[] { LanguageNames.CSharp, LanguageNames.VisualBasic } : new string?[] { null }; - - foreach (var language in languages) - { - updatedOptions = updatedOptions.WithChangedOption(new OptionKey(option, language), newValue); - } - } - - return updatedOptions; - } - static void ValidateCommonOptions(SyntaxFormattingOptions formattingOptions) { Assert.True(formattingOptions.UseTabs); diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestOptionService.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestOptionService.cs deleted file mode 100644 index 91e0d34ef6c5d..0000000000000 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestOptionService.cs +++ /dev/null @@ -1,71 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Microsoft.CodeAnalysis.Test.Utilities; - -namespace Microsoft.CodeAnalysis.UnitTests -{ - internal static class TestOptionService - { - public static IGlobalOptionService GetGlobalOptionService(HostWorkspaceServices services, IOptionProvider? optionProvider = null, IOptionPersisterProvider? optionPersisterProvider = null) - { - var mefHostServices = (IMefHostExportProvider)services.HostServices; - var workspaceThreadingService = mefHostServices.GetExportedValues().SingleOrDefault(); - return new GlobalOptionService( - workspaceThreadingService, - new[] - { - new Lazy(() => optionProvider ??= new TestOptionsProvider(), new LanguageMetadata(LanguageNames.CSharp)) - }, - new[] - { - new Lazy(() => optionPersisterProvider ??= new TestOptionsPersisterProvider()) - }); - } - - public static OptionServiceFactory.OptionService GetService(Workspace workspace, IOptionProvider? optionProvider = null, IOptionPersisterProvider? optionPersisterProvider = null) - => new OptionServiceFactory.OptionService(GetGlobalOptionService(workspace.Services, optionProvider, optionPersisterProvider)); - - internal class TestOptionsProvider : IOptionProvider - { - public ImmutableArray Options { get; } = ImmutableArray.Create( - new Option("Test Feature", "Test Name", false)); - } - - internal sealed class TestOptionsPersisterProvider : IOptionPersisterProvider - { - private readonly ValueTask _optionPersisterTask; - - public TestOptionsPersisterProvider(IOptionPersister? optionPersister = null) - => _optionPersisterTask = new(optionPersister ?? new TestOptionsPersister()); - - public ValueTask GetOrCreatePersisterAsync(CancellationToken cancellationToken) - => _optionPersisterTask; - } - - internal sealed class TestOptionsPersister : IOptionPersister - { - private ImmutableDictionary _options = ImmutableDictionary.Empty; - - public bool TryFetch(OptionKey optionKey, out object? value) - => _options.TryGetValue(optionKey, out value); - - public bool TryPersist(OptionKey optionKey, object? value) - { - _options = _options.SetItem(optionKey, value); - return true; - } - } - } -} diff --git a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestOptionsServiceFactory.cs b/src/Workspaces/CoreTest/Host/WorkspaceServices/TestOptionsServiceFactory.cs deleted file mode 100644 index 9f990cc0e00c1..0000000000000 --- a/src/Workspaces/CoreTest/Host/WorkspaceServices/TestOptionsServiceFactory.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Composition; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Options; -using Microsoft.CodeAnalysis.Options.Providers; -using Microsoft.CodeAnalysis.Shared.Utilities; -using Roslyn.Utilities; - -namespace Microsoft.CodeAnalysis.UnitTests -{ - [ExportWorkspaceServiceFactory(typeof(IOptionService), ServiceLayer.Host), Shared] - internal class TestOptionsServiceFactory : IWorkspaceServiceFactory - { - private readonly IWorkspaceThreadingService? _workspaceThreadingService; - private readonly ImmutableArray> _providers; - - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public TestOptionsServiceFactory( - [Import(AllowDefault = true)] IWorkspaceThreadingService? workspaceThreadingService, - [ImportMany] IEnumerable> optionProviders) - { - _workspaceThreadingService = workspaceThreadingService; - _providers = optionProviders.ToImmutableArray(); - } - - public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) - { - // give out new option service per workspace - return new OptionServiceFactory.OptionService( - new GlobalOptionService(_workspaceThreadingService, _providers, SpecializedCollections.EmptyEnumerable>())); - } - } -} diff --git a/src/Workspaces/CoreTest/Options/OptionsTestHelpers.cs b/src/Workspaces/CoreTest/Options/OptionsTestHelpers.cs new file mode 100644 index 0000000000000..1c59b00c22ddf --- /dev/null +++ b/src/Workspaces/CoreTest/Options/OptionsTestHelpers.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Formatting; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Options; + +namespace Microsoft.CodeAnalysis.UnitTests +{ + internal static class OptionsTestHelpers + { + public static readonly Option CustomPublicOption = new Option("My Feature", "My Option", defaultValue: true); + + // all public options and their non-default values: + public static readonly ImmutableArray<(IOption, object)> PublicCustomOptionsWithNonDefaultValues = ImmutableArray.Create<(IOption, object)>( + (CustomPublicOption, false)); + + public static readonly ImmutableArray<(IOption, object)> PublicAutoFormattingOptionsWithNonDefaultValues = ImmutableArray.Create<(IOption, object)>( + (FormattingOptions.SmartIndent, FormattingOptions2.IndentStyle.Block)); + + public static readonly ImmutableArray<(IOption, object)> PublicFormattingOptionsWithNonDefaultValues = ImmutableArray.Create<(IOption, object)>( + (FormattingOptions.UseTabs, true), + (FormattingOptions.TabSize, 5), + (FormattingOptions.IndentationSize, 7), + (FormattingOptions.NewLine, "\r"), + (CSharpFormattingOptions.IndentBlock, false), + (CSharpFormattingOptions.IndentBraces, true), + (CSharpFormattingOptions.IndentSwitchCaseSection, false), + (CSharpFormattingOptions.IndentSwitchCaseSectionWhenBlock, false), + (CSharpFormattingOptions.IndentSwitchSection, false), + (CSharpFormattingOptions.LabelPositioning, LabelPositionOptions.LeftMost), + (CSharpFormattingOptions.NewLineForCatch, false), + (CSharpFormattingOptions.NewLineForClausesInQuery, false), + (CSharpFormattingOptions.NewLineForElse, false), + (CSharpFormattingOptions.NewLineForFinally, false), + (CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, false), + (CSharpFormattingOptions.NewLineForMembersInObjectInit, false), + (CSharpFormattingOptions.NewLinesForBracesInAccessors, false), + (CSharpFormattingOptions.NewLinesForBracesInAnonymousMethods, false), + (CSharpFormattingOptions.NewLinesForBracesInAnonymousTypes, false), + (CSharpFormattingOptions.NewLinesForBracesInControlBlocks, false), + (CSharpFormattingOptions.NewLinesForBracesInLambdaExpressionBody, false), + (CSharpFormattingOptions.NewLinesForBracesInMethods, false), + (CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers, false), + (CSharpFormattingOptions.NewLinesForBracesInProperties, false), + (CSharpFormattingOptions.NewLinesForBracesInTypes, false), + (CSharpFormattingOptions.SpaceAfterCast, true), + (CSharpFormattingOptions.SpaceAfterColonInBaseTypeDeclaration, false), + (CSharpFormattingOptions.SpaceAfterComma, false), + (CSharpFormattingOptions.SpaceAfterControlFlowStatementKeyword, false), + (CSharpFormattingOptions.SpaceAfterDot, true), + (CSharpFormattingOptions.SpaceAfterMethodCallName, true), + (CSharpFormattingOptions.SpaceAfterSemicolonsInForStatement, false), + (CSharpFormattingOptions.SpaceBeforeColonInBaseTypeDeclaration, false), + (CSharpFormattingOptions.SpaceBeforeComma, true), + (CSharpFormattingOptions.SpaceBeforeDot, true), + (CSharpFormattingOptions.SpaceBeforeOpenSquareBracket, true), + (CSharpFormattingOptions.SpaceBeforeSemicolonsInForStatement, true), + (CSharpFormattingOptions.SpaceBetweenEmptyMethodCallParentheses, true), + (CSharpFormattingOptions.SpaceBetweenEmptyMethodDeclarationParentheses, true), + (CSharpFormattingOptions.SpaceBetweenEmptySquareBrackets, true), + (CSharpFormattingOptions.SpacesIgnoreAroundVariableDeclaration, true), + (CSharpFormattingOptions.SpaceWithinCastParentheses, true), + (CSharpFormattingOptions.SpaceWithinExpressionParentheses, true), + (CSharpFormattingOptions.SpaceWithinMethodCallParentheses, true), + (CSharpFormattingOptions.SpaceWithinMethodDeclarationParenthesis, true), + (CSharpFormattingOptions.SpaceWithinOtherParentheses, true), + (CSharpFormattingOptions.SpaceWithinSquareBrackets, true), + (CSharpFormattingOptions.SpacingAfterMethodDeclarationName, true), + (CSharpFormattingOptions.SpacingAroundBinaryOperator, BinaryOperatorSpacingOptions.Remove), + (CSharpFormattingOptions.WrappingKeepStatementsOnSingleLine, false), + (CSharpFormattingOptions.WrappingPreserveSingleLine, false)); + + public static readonly ImmutableArray<(IOption, object)> PublicCodeStyleOptionsWithNonDefaultValues = ImmutableArray.Create<(IOption, object)>( + (CodeStyleOptions.QualifyFieldAccess, new CodeStyleOption(true, NotificationOption.Suggestion)), + (CodeStyleOptions.QualifyPropertyAccess, new CodeStyleOption(true, NotificationOption.Suggestion)), + (CodeStyleOptions.QualifyMethodAccess, new CodeStyleOption(true, NotificationOption.Suggestion)), + (CodeStyleOptions.QualifyEventAccess, new CodeStyleOption(true, NotificationOption.Suggestion)), + (CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInDeclaration, new CodeStyleOption(false, NotificationOption.Suggestion)), + (CodeStyleOptions.PreferIntrinsicPredefinedTypeKeywordInMemberAccess, new CodeStyleOption(false, NotificationOption.Suggestion))); + + public static readonly IEnumerable<(IOption, object)> AllPublicOptionsWithNonDefaultValues = + PublicCustomOptionsWithNonDefaultValues + .Concat(PublicAutoFormattingOptionsWithNonDefaultValues) + .Concat(PublicFormattingOptionsWithNonDefaultValues) + .Concat(PublicCodeStyleOptionsWithNonDefaultValues); + + public static OptionSet GetOptionSetWithChangedOptions(OptionSet options, IEnumerable<(IOption, object)> optionsWithNonDefaultValues) + { + var updatedOptions = options; + foreach (var (option, newValue) in optionsWithNonDefaultValues) + { + foreach (var language in GetApplicableLanguages(option)) + { + updatedOptions = updatedOptions.WithChangedOption(new OptionKey(option, language), newValue); + } + } + + return updatedOptions; + } + + public static IEnumerable GetApplicableLanguages(IOption option) + => (option is IPerLanguageOption) ? new[] { LanguageNames.CSharp, LanguageNames.VisualBasic } : new string?[] { null }; + } +} diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs index 65e77d5054e9a..3e4e17615f466 100644 --- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs +++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs @@ -35,6 +35,7 @@ using Xunit; using CS = Microsoft.CodeAnalysis.CSharp; using static Microsoft.CodeAnalysis.UnitTests.SolutionTestHelpers; +using Microsoft.CodeAnalysis.Indentation; namespace Microsoft.CodeAnalysis.UnitTests { @@ -3265,16 +3266,16 @@ private static void GetMultipleProjects( projectReferences: new[] { new ProjectReference(dependsOnVbNormalProject.Id) })); } - [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + [Fact] public void TestOptionChangesForLanguagesNotInSolution() { // Create an empty solution with no projects. using var workspace = CreateWorkspace(); var s0 = workspace.CurrentSolution; - var optionService = workspace.Services.GetRequiredService(); + var optionService = workspace.Services.GetRequiredService(); // Apply an option change to a C# option. - var option = GenerationOptions.PlaceSystemNamespaceFirst; + var option = FormattingOptions.UseTabs; var defaultValue = option.DefaultValue; var changedValue = !defaultValue; var options = s0.Options.WithChangedOption(option, LanguageNames.CSharp, changedValue); diff --git a/src/Workspaces/CoreTest/TestCompositionTests.cs b/src/Workspaces/CoreTest/TestCompositionTests.cs index a9801c2c3a40e..2169eb703246d 100644 --- a/src/Workspaces/CoreTest/TestCompositionTests.cs +++ b/src/Workspaces/CoreTest/TestCompositionTests.cs @@ -15,8 +15,8 @@ public class TestCompositionTests [Fact] public void FactoryReuse() { - var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestProjectCacheService), typeof(TestOptionsServiceFactory)); - var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestOptionsServiceFactory), typeof(TestProjectCacheService)); + var composition1 = FeaturesTestCompositions.Features.AddParts(typeof(TestProjectCacheService), typeof(TestTemporaryStorageServiceFactory)); + var composition2 = FeaturesTestCompositions.Features.AddParts(typeof(TestTemporaryStorageServiceFactory), typeof(TestProjectCacheService)); Assert.Same(composition1.ExportProviderFactory, composition2.ExportProviderFactory); } diff --git a/src/Workspaces/CoreTest/WorkspaceServiceTests/OptionServiceTests.cs b/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs similarity index 71% rename from src/Workspaces/CoreTest/WorkspaceServiceTests/OptionServiceTests.cs rename to src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs index 2722f2ed826fd..ba56090b487a3 100644 --- a/src/Workspaces/CoreTest/WorkspaceServiceTests/OptionServiceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceServiceTests/GlobalOptionServiceTests.cs @@ -8,12 +8,14 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Options.Providers; +using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Roslyn.Utilities; @@ -22,14 +24,84 @@ namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceServices { [UseExportProvider] - public class OptionServiceTests + public class GlobalOptionServiceTests { + private static IGlobalOptionService GetGlobalOptionService(HostWorkspaceServices services, IOptionProvider? optionProvider = null, IOptionPersisterProvider? optionPersisterProvider = null) + { + var mefHostServices = (IMefHostExportProvider)services.HostServices; + var workspaceThreadingService = mefHostServices.GetExportedValues().SingleOrDefault(); + return new GlobalOptionService( + workspaceThreadingService, + new[] + { + new Lazy(() => optionProvider ??= new TestOptionsProvider(), new LanguageMetadata(LanguageNames.CSharp)) + }, + new[] + { + new Lazy(() => optionPersisterProvider ??= new TestOptionsPersisterProvider()) + }); + } + + private static ILegacyWorkspaceOptionService GetOptionService(HostWorkspaceServices services, IOptionProvider? optionProvider = null, IOptionPersisterProvider? optionPersisterProvider = null) + => new TestService(GetGlobalOptionService(services, optionProvider, optionPersisterProvider)); + + private sealed class TestService : ILegacyWorkspaceOptionService + { + public IGlobalOptionService GlobalOptions { get; } + + public TestService(IGlobalOptionService globalOptions) + => GlobalOptions = globalOptions; + + public object? GetOption(OptionKey key) + => GlobalOptions.GetOption(key); + + public void SetOptions(OptionSet optionSet, IEnumerable optionKeys) + => GlobalOptions.SetOptions(optionSet, optionKeys); + + public void RegisterWorkspace(Workspace workspace) + => throw new NotImplementedException(); + + public void UnregisterWorkspace(Workspace workspace) + => throw new NotImplementedException(); + } + + internal class TestOptionsProvider : IOptionProvider + { + public ImmutableArray Options { get; } = ImmutableArray.Create( + new Option("Test Feature", "Test Name", false)); + } + + internal sealed class TestOptionsPersisterProvider : IOptionPersisterProvider + { + private readonly ValueTask _optionPersisterTask; + + public TestOptionsPersisterProvider(IOptionPersister? optionPersister = null) + => _optionPersisterTask = new(optionPersister ?? new TestOptionsPersister()); + + public ValueTask GetOrCreatePersisterAsync(CancellationToken cancellationToken) + => _optionPersisterTask; + } + + internal sealed class TestOptionsPersister : IOptionPersister + { + private ImmutableDictionary _options = ImmutableDictionary.Empty; + + public bool TryFetch(OptionKey optionKey, out object? value) + => _options.TryGetValue(optionKey, out value); + + public bool TryPersist(OptionKey optionKey, object? value) + { + _options = _options.SetItem(optionKey, value); + return true; + } + } + [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] public void OptionWithNullOrWhitespace() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); - var optionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var optionSet = new SolutionOptionSet(optionService); Assert.Throws(delegate { @@ -56,8 +128,8 @@ public void OptionWithNullOrWhitespace() public void OptionPerLanguageOption() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); - var optionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var optionSet = new SolutionOptionSet(optionService); Assert.Throws(delegate { @@ -87,8 +159,8 @@ public void OptionPerLanguageOption() public void GettingOptionReturnsOption() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); - var optionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var optionSet = new SolutionOptionSet(optionService); var option = new Option("Test Feature", "Test Name", false); Assert.False(optionSet.GetOption(option)); } @@ -97,7 +169,7 @@ public void GettingOptionReturnsOption() public void GlobalOptions() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetGlobalOptionService(workspace.Services); + var globalOptions = GetGlobalOptionService(workspace.Services); var option1 = new Option("Feature1", "Name1", defaultValue: 1); var option2 = new Option("Feature2", "Name2", defaultValue: 2); var option3 = new Option("Feature3", "Name3", defaultValue: 3); @@ -105,13 +177,13 @@ public void GlobalOptions() var changedOptions = new List(); var handler = new EventHandler((_, e) => changedOptions.Add(e)); - optionService.OptionChanged += handler; + globalOptions.OptionChanged += handler; - var values = optionService.GetOptions(ImmutableArray.Create(option1, option2)); + var values = globalOptions.GetOptions(ImmutableArray.Create(option1, option2)); Assert.Equal(1, values[0]); Assert.Equal(2, values[1]); - optionService.SetGlobalOptions( + globalOptions.SetGlobalOptions( ImmutableArray.Create(option1, option2, option3), ImmutableArray.Create(5, 6, 3)); @@ -121,24 +193,24 @@ public void GlobalOptions() "Name2=6", }, changedOptions.Select(e => $"{e.OptionKey.Option.Name}={e.Value}")); - values = optionService.GetOptions(ImmutableArray.Create(option1, option2, option3)); + values = globalOptions.GetOptions(ImmutableArray.Create(option1, option2, option3)); Assert.Equal(5, values[0]); Assert.Equal(6, values[1]); Assert.Equal(3, values[2]); - Assert.Equal(5, optionService.GetOption(option1)); - Assert.Equal(6, optionService.GetOption(option2)); - Assert.Equal(3, optionService.GetOption(option3)); + Assert.Equal(5, globalOptions.GetOption(option1)); + Assert.Equal(6, globalOptions.GetOption(option2)); + Assert.Equal(3, globalOptions.GetOption(option3)); - optionService.OptionChanged -= handler; + globalOptions.OptionChanged -= handler; } [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] public void GettingOptionWithChangedOption() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); - OptionSet optionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + OptionSet optionSet = new SolutionOptionSet(optionService); var option = new Option("Test Feature", "Test Name", false); var key = new OptionKey(option); Assert.False(optionSet.GetOption(option)); @@ -150,8 +222,8 @@ public void GettingOptionWithChangedOption() public void GettingOptionWithoutChangedOption() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); - var optionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var optionSet = new SolutionOptionSet(optionService); var optionFalse = new Option("Test Feature", "Test Name", false); Assert.False(optionSet.GetOption(optionFalse)); @@ -170,11 +242,11 @@ public void GettingOptionWithoutChangedOption() public void GetKnownOptions() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); + var optionService = GetOptionService(workspace.Services); var option = new Option("Test Feature", "Test Name", defaultValue: true); optionService.GetOption(option); - var optionSet = optionService.GetOptions(); + var optionSet = new SolutionOptionSet(optionService); var value = optionSet.GetOption(option); Assert.True(value); } @@ -183,11 +255,11 @@ public void GetKnownOptions() public void GetKnownOptionsKey() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); + var optionService = GetOptionService(workspace.Services); var option = new Option("Test Feature", "Test Name", defaultValue: true); optionService.GetOption(option); - var optionSet = optionService.GetOptions(); + var optionSet = new SolutionOptionSet(optionService); var optionKey = new OptionKey(option); var value = (bool?)optionSet.GetOption(optionKey); Assert.True(value); @@ -197,14 +269,14 @@ public void GetKnownOptionsKey() public void SetKnownOptions() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); - var optionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var optionSet = new SolutionOptionSet(optionService); var option = new Option("Test Feature", "Test Name", defaultValue: true); var optionKey = new OptionKey(option); var newOptionSet = optionSet.WithChangedOption(optionKey, false); - optionService.SetOptions(newOptionSet); - var isOptionSet = (bool?)optionService.GetOptions().GetOption(optionKey); + optionService.GlobalOptions.SetOptions(newOptionSet, ((SolutionOptionSet)newOptionSet).GetChangedOptions()); + var isOptionSet = (bool?)new SolutionOptionSet(optionService).GetOption(optionKey); Assert.False(isOptionSet); } @@ -212,8 +284,8 @@ public void SetKnownOptions() public void OptionSetIsImmutable() { using var workspace = new AdhocWorkspace(); - var optionService = TestOptionService.GetService(workspace); - var optionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var optionSet = new SolutionOptionSet(optionService); var option = new Option("Test Feature", "Test Name", defaultValue: true); var optionKey = new OptionKey(option); @@ -245,8 +317,8 @@ public void TestPerLanguageCodeStyleOptions() // 4. { PerLanguageOption2, CodeStyleOption2 } TestCodeStyleOptionsCommon(workspace, perLanguageOption2, LanguageNames.CSharp, newValueCodeStyleOption2); - var optionService = TestOptionService.GetService(workspace); - var originalOptionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var originalOptionSet = new SolutionOptionSet(optionService); // Test "PerLanguageOption" and "PerLanguageOption2" overloads for OptionSet and OptionService. @@ -264,9 +336,10 @@ public void TestPerLanguageCodeStyleOptions() Assert.Equal(newValueCodeStyleOption2, newOptionSet.GetOption(perLanguageOption2, LanguageNames.CSharp)); // 3. IOptionService validation - optionService.SetOptions(newOptionSet); - Assert.Equal(newValueCodeStyleOption, optionService.GetOption(perLanguageOption, LanguageNames.CSharp)); - Assert.Equal(newValueCodeStyleOption2, optionService.GetOption(perLanguageOption2, LanguageNames.CSharp)); + + optionService.GlobalOptions.SetOptions(newOptionSet, ((SolutionOptionSet)newOptionSet).GetChangedOptions()); + Assert.Equal(newValueCodeStyleOption, optionService.GlobalOptions.GetOption(perLanguageOption, LanguageNames.CSharp)); + Assert.Equal(newValueCodeStyleOption2, optionService.GlobalOptions.GetOption(perLanguageOption2, LanguageNames.CSharp)); } [Fact] @@ -292,8 +365,8 @@ public void TestLanguageSpecificCodeStyleOptions() // 4. { Option2, CodeStyleOption2 } TestCodeStyleOptionsCommon(workspace, option2, language: null, newValueCodeStyleOption2); - var optionService = TestOptionService.GetService(workspace); - var originalOptionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var originalOptionSet = new SolutionOptionSet(optionService); // Test "Option" and "Option2" overloads for OptionSet and OptionService. @@ -311,16 +384,16 @@ public void TestLanguageSpecificCodeStyleOptions() Assert.Equal(newValueCodeStyleOption2, newOptionSet.GetOption(option2)); // 3. IOptionService validation - optionService.SetOptions(newOptionSet); - Assert.Equal(newValueCodeStyleOption, optionService.GetOption(option)); - Assert.Equal(newValueCodeStyleOption2, optionService.GetOption(option2)); + optionService.GlobalOptions.SetOptions(newOptionSet, ((SolutionOptionSet)newOptionSet).GetChangedOptions()); + Assert.Equal(newValueCodeStyleOption, optionService.GlobalOptions.GetOption(option)); + Assert.Equal(newValueCodeStyleOption2, optionService.GlobalOptions.GetOption(option2)); } private static void TestCodeStyleOptionsCommon(Workspace workspace, IOption2 option, string? language, TCodeStyleOption newValue) where TCodeStyleOption : ICodeStyleOption { - var optionService = TestOptionService.GetService(workspace); - var originalOptionSet = optionService.GetOptions(); + var optionService = GetOptionService(workspace.Services); + var originalOptionSet = new SolutionOptionSet(optionService); // Test matrix using different OptionKey and OptionKey2 get/set operations. var optionKey = new OptionKey(option, language); @@ -362,7 +435,7 @@ private static void TestCodeStyleOptionsCommon(Workspace works Assert.Equal(newValue, newOptionSet.GetOption(optionKey2)); // 5. IOptionService.GetOption(OptionKey) - optionService.SetOptions(newOptionSet); + optionService.GlobalOptions.SetOptions(newOptionSet, ((SolutionOptionSet)newOptionSet).GetChangedOptions()); Assert.Equal(newPublicValue, optionService.GetOption(optionKey)); } } diff --git a/src/Workspaces/CoreTest/WorkspaceTests/WorkspaceReferenceTests.cs b/src/Workspaces/CoreTest/WorkspaceTests/WorkspaceReferenceTests.cs index 8ff7b07971006..d5d7f8c52da9b 100644 --- a/src/Workspaces/CoreTest/WorkspaceTests/WorkspaceReferenceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceTests/WorkspaceReferenceTests.cs @@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.UnitTests.WorkspaceTests +namespace Microsoft.CodeAnalysis.UnitTests { [UseExportProvider] public class WorkspaceReferenceTests diff --git a/src/Workspaces/CoreTest/WorkspaceTests/GeneralWorkspaceTests.cs b/src/Workspaces/CoreTest/WorkspaceTests/WorkspaceTests.cs similarity index 85% rename from src/Workspaces/CoreTest/WorkspaceTests/GeneralWorkspaceTests.cs rename to src/Workspaces/CoreTest/WorkspaceTests/WorkspaceTests.cs index 725174528d573..bf1ca24f0ebea 100644 --- a/src/Workspaces/CoreTest/WorkspaceTests/GeneralWorkspaceTests.cs +++ b/src/Workspaces/CoreTest/WorkspaceTests/WorkspaceTests.cs @@ -9,13 +9,17 @@ using Microsoft.CodeAnalysis.Text; using Xunit; using System; +using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Indentation; +using Microsoft.CodeAnalysis.Editor.UnitTests.Formating; namespace Microsoft.CodeAnalysis.UnitTests { - [UseExportProvider] - public class GeneralWorkspaceTests + [UseExportProvider, Trait(Traits.Feature, Traits.Features.Workspace)] + public class WorkspaceTests { - [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + [Fact] public void TestChangeDocumentContent_TryApplyChanges_Throws() { using var ws = new NoChangesAllowedWorkspace(); @@ -39,7 +43,7 @@ public void TestChangeDocumentContent_TryApplyChanges_Throws() Assert.Equal(WorkspacesResources.Changing_documents_is_not_supported, e.Message); } - [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + [Fact] public void TestChangeDocumentName_TryApplyChanges_Throws() { using var ws = new NoChangesAllowedWorkspace(); @@ -65,7 +69,7 @@ public void TestChangeDocumentName_TryApplyChanges_Throws() Assert.Equal(WorkspacesResources.Changing_document_property_is_not_supported, e.Message); } - [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + [Fact] public void TestChangeDocumentFolders_TryApplyChanges_Throws() { using var ws = new NoChangesAllowedWorkspace(); @@ -93,7 +97,7 @@ public void TestChangeDocumentFolders_TryApplyChanges_Throws() Assert.Equal(WorkspacesResources.Changing_document_property_is_not_supported, e.Message); } - [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + [Fact] public void TestChangeDocumentFilePath_TryApplyChanges_Throws() { using var ws = new NoChangesAllowedWorkspace(); @@ -120,7 +124,7 @@ public void TestChangeDocumentFilePath_TryApplyChanges_Throws() Assert.Equal(WorkspacesResources.Changing_document_property_is_not_supported, e.Message); } - [Fact, Trait(Traits.Feature, Traits.Features.Workspace)] + [Fact] public void TestChangeDocumentSourceCodeKind_TryApplyChanges_Throws() { using var ws = new NoChangesAllowedWorkspace(); @@ -219,5 +223,26 @@ public Document AddDocument(DocumentInfo documentInfo) return this.CurrentSolution.GetDocument(documentInfo.Id); } } + + [Fact, Obsolete] + public void SetOptions_PublicGlobalOptions() + { + using var workspace1 = new AdhocWorkspace(); + var solution = workspace1.CurrentSolution; + + var newOptions = OptionsTestHelpers.GetOptionSetWithChangedOptions(solution.Options, OptionsTestHelpers.AllPublicOptionsWithNonDefaultValues); + + // Sets options to global options that are shared among all workspaces: + workspace1.Options = newOptions; + + using var workspace2 = new AdhocWorkspace(); + foreach (var (option, value) in OptionsTestHelpers.AllPublicOptionsWithNonDefaultValues) + { + foreach (var language in OptionsTestHelpers.GetApplicableLanguages(option)) + { + Assert.Equal(value, workspace2.Options.GetOption(new OptionKey(option, language))); + } + } + } } } diff --git a/src/Workspaces/CoreTestUtilities/OptionsCollection.cs b/src/Workspaces/CoreTestUtilities/OptionsCollection.cs index 59fdacd317a5b..4f36367bab2e7 100644 --- a/src/Workspaces/CoreTestUtilities/OptionsCollection.cs +++ b/src/Workspaces/CoreTestUtilities/OptionsCollection.cs @@ -76,7 +76,7 @@ public OptionSet ToOptionSet() public AnalyzerConfigOptions ToAnalyzerConfigOptions(HostLanguageServices languageServices) { - var optionService = languageServices.WorkspaceServices.GetRequiredService(); + var optionService = languageServices.WorkspaceServices.GetRequiredService(); return ToOptionSet().AsAnalyzerConfigOptions(optionService, languageServices.Language); } diff --git a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs index 3ec33c62aa77d..5b27f7bcfdac5 100644 --- a/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs +++ b/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspace.cs @@ -337,19 +337,19 @@ private Solution CreateSolutionFromInfo(SolutionInfo solutionInfo) // if either solution id or file path changed, then we consider it as new solution. Otherwise, // update the current solution in place. - var oldSolution = this.CurrentSolution; + var oldSolution = CurrentSolution; var addingSolution = oldSolution.Id != newSolution.Id || oldSolution.FilePath != newSolution.FilePath; if (addingSolution) { // We're not doing an update, we're moving to a new solution entirely. Clear out the old one. This // is necessary so that we clear out any open document information this workspace is tracking. Note: // this seems suspect as the remote workspace should not be tracking any open document state. - this.ClearSolutionData(); + ClearSolutionData(); } newSolution = SetCurrentSolution(newSolution); - SetOptions(newSolution.Options); - _ = this.RaiseWorkspaceChangedEventAsync( + + _ = RaiseWorkspaceChangedEventAsync( addingSolution ? WorkspaceChangeKind.SolutionAdded : WorkspaceChangeKind.SolutionChanged, oldSolution, newSolution); return (newSolution, updated: true); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs index 6e0cb6c69926e..1a4705f640652 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/CodeStyle/CodeStyleOption2`1.cs @@ -11,7 +11,7 @@ namespace Microsoft.CodeAnalysis.CodeStyle { - internal interface ICodeStyleOption : IObjectWritable + internal interface ICodeStyleOption { XElement ToXElement(); object? Value { get; } @@ -41,15 +41,16 @@ internal interface ICodeStyleOption : IObjectWritable [DataContract] internal sealed partial class CodeStyleOption2 : ICodeStyleOption, IEquatable?> { - static CodeStyleOption2() - { - ObjectBinder.RegisterTypeReader(typeof(CodeStyleOption2), ReadFrom); - } - public static readonly CodeStyleOption2 Default = new(default!, NotificationOption2.Silent); private const int SerializationVersion = 1; + private const string XmlElement_CodeStyleOption = "CodeStyleOption"; + private const string XmlAttribute_SerializationVersion = "SerializationVersion"; + private const string XmlAttribute_Type = "Type"; + private const string XmlAttribute_Value = "Value"; + private const string XmlAttribute_DiagnosticSeverity = "DiagnosticSeverity"; + [DataMember(Order = 0)] public T Value { get; } @@ -77,11 +78,11 @@ ICodeStyleOption ICodeStyleOption.AsCodeStyleOption() private int EnumValueAsInt32 => (int)(object)Value!; public XElement ToXElement() => - new("CodeStyleOption", // Ensure that we use "CodeStyleOption" as the name for back compat. - new XAttribute(nameof(SerializationVersion), SerializationVersion), - new XAttribute("Type", GetTypeNameForSerialization()), - new XAttribute(nameof(Value), GetValueForSerialization()), - new XAttribute(nameof(DiagnosticSeverity), Notification.Severity.ToDiagnosticSeverity() ?? DiagnosticSeverity.Hidden)); + new(XmlElement_CodeStyleOption, // Ensure that we use "CodeStyleOption" as the name for back compat. + new XAttribute(XmlAttribute_SerializationVersion, SerializationVersion), + new XAttribute(XmlAttribute_Type, GetTypeNameForSerialization()), + new XAttribute(XmlAttribute_Value, GetValueForSerialization()), + new XAttribute(XmlAttribute_DiagnosticSeverity, Notification.Severity.ToDiagnosticSeverity() ?? DiagnosticSeverity.Hidden)); private object GetValueForSerialization() { @@ -128,10 +129,10 @@ private bool IsZeroOrOneValueOfEnum() public static CodeStyleOption2 FromXElement(XElement element) { - var typeAttribute = element.Attribute("Type"); - var valueAttribute = element.Attribute(nameof(Value)); - var severityAttribute = element.Attribute(nameof(DiagnosticSeverity)); - var version = (int?)element.Attribute(nameof(SerializationVersion)); + var typeAttribute = element.Attribute(XmlAttribute_Type); + var valueAttribute = element.Attribute(XmlAttribute_Value); + var severityAttribute = element.Attribute(XmlAttribute_DiagnosticSeverity); + var version = (int?)element.Attribute(XmlAttribute_SerializationVersion); if (typeAttribute == null || valueAttribute == null || severityAttribute == null) { @@ -158,28 +159,6 @@ public static CodeStyleOption2 FromXElement(XElement element) }); } - public bool ShouldReuseInSerialization => false; - - public void WriteTo(ObjectWriter writer) - { - writer.WriteValue(GetValueForSerialization()); - writer.WriteInt32((int)(Notification.Severity.ToDiagnosticSeverity() ?? DiagnosticSeverity.Hidden)); - } - - public static CodeStyleOption2 ReadFrom(ObjectReader reader) - { - return new CodeStyleOption2( - reader.ReadValue(), - (DiagnosticSeverity)reader.ReadInt32() switch - { - DiagnosticSeverity.Hidden => NotificationOption2.Silent, - DiagnosticSeverity.Info => NotificationOption2.Suggestion, - DiagnosticSeverity.Warning => NotificationOption2.Warning, - DiagnosticSeverity.Error => NotificationOption2.Error, - var v => throw ExceptionUtilities.UnexpectedValue(v), - }); - } - private static Func GetParser(string type) => type switch { diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferences.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferences.cs index dd2bb8b9c3dfb..0a1f0b31644a4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferences.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/NamingStyles/Serialization/NamingStylePreferences.cs @@ -23,13 +23,8 @@ namespace Microsoft.CodeAnalysis.Diagnostics.Analyzers.NamingStyles /// 3. Naming Rule (points to Symbol Specification IDs) /// [DataContract] - internal sealed class NamingStylePreferences : IEquatable, IObjectWritable + internal sealed class NamingStylePreferences : IEquatable { - static NamingStylePreferences() - { - ObjectBinder.RegisterTypeReader(typeof(NamingStylePreferences), ReadFrom); - } - private const int s_serializationVersion = 5; [DataMember(Order = 0)] @@ -78,9 +73,9 @@ internal XElement CreateXElement() { return new XElement("NamingPreferencesInfo", new XAttribute("SerializationVersion", s_serializationVersion), - new XElement(nameof(SymbolSpecifications), SymbolSpecifications.Select(s => s.CreateXElement())), - new XElement(nameof(NamingStyles), NamingStyles.Select(n => n.CreateXElement())), - new XElement(nameof(NamingRules), NamingRules.Select(n => n.CreateXElement()))); + new XElement("SymbolSpecifications", SymbolSpecifications.Select(s => s.CreateXElement())), + new XElement("NamingStyles", NamingStyles.Select(n => n.CreateXElement())), + new XElement("NamingRules", NamingRules.Select(n => n.CreateXElement()))); } internal static NamingStylePreferences FromXElement(XElement element) @@ -88,31 +83,14 @@ internal static NamingStylePreferences FromXElement(XElement element) element = GetUpgradedSerializationIfNecessary(element); return new NamingStylePreferences( - element.Element(nameof(SymbolSpecifications)).Elements(nameof(SymbolSpecification)) + element.Element("SymbolSpecifications").Elements(nameof(SymbolSpecification)) .Select(SymbolSpecification.FromXElement).ToImmutableArray(), - element.Element(nameof(NamingStyles)).Elements(nameof(NamingStyle)) + element.Element("NamingStyles").Elements(nameof(NamingStyle)) .Select(NamingStyle.FromXElement).ToImmutableArray(), - element.Element(nameof(NamingRules)).Elements(nameof(SerializableNamingRule)) + element.Element("NamingRules").Elements(nameof(SerializableNamingRule)) .Select(SerializableNamingRule.FromXElement).ToImmutableArray()); } - public bool ShouldReuseInSerialization => false; - - public void WriteTo(ObjectWriter writer) - { - writer.WriteArray(SymbolSpecifications, (w, v) => v.WriteTo(w)); - writer.WriteArray(NamingStyles, (w, v) => v.WriteTo(w)); - writer.WriteArray(NamingRules, (w, v) => v.WriteTo(w)); - } - - public static NamingStylePreferences ReadFrom(ObjectReader reader) - { - return new NamingStylePreferences( - reader.ReadArray(SymbolSpecification.ReadFrom), - reader.ReadArray(NamingStyle.ReadFrom), - reader.ReadArray(SerializableNamingRule.ReadFrom)); - } - public override bool Equals(object obj) => Equals(obj as NamingStylePreferences);