From d81aa5ab403277bde143a5cf87b02c5e1e801150 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Sep 2022 15:02:30 -0700 Subject: [PATCH 1/9] Replace Razor Source Generator reference with one loaded from a VSIX --- ...tudioDiagnosticAnalyzerProvider.Factory.cs | 65 +++++++++ ...AnalyzerProvider.WorkspaceEventListener.cs | 35 +---- .../VisualStudioDiagnosticAnalyzerProvider.cs | 23 ++-- .../Def/ProjectSystem/VisualStudioAnalyzer.cs | 10 +- .../Def/ProjectSystem/VisualStudioProject.cs | 123 +++++++++++------- .../VisualStudioProjectFactory.cs | 7 + .../Test/Diagnostics/MockExtensionManager.vb | 5 +- ...alStudioDiagnosticAnalyzerProviderTests.vb | 8 +- .../VisualStudioAnalyzerTests.vb | 2 + 9 files changed, 189 insertions(+), 89 deletions(-) create mode 100644 src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs new file mode 100644 index 0000000000000..b39e109faf709 --- /dev/null +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs @@ -0,0 +1,65 @@ +// 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.Composition; +using System.Reflection; +using System.Threading.Tasks; +using Roslyn.Utilities; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Editor.Shared.Utilities; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics +{ + internal partial class VisualStudioDiagnosticAnalyzerProvider + { + [Export(typeof(Factory)), Shared] + internal sealed class Factory + { + private readonly IThreadingContext _threadingContext; + private readonly IServiceProvider _serviceProvider; + + private VisualStudioDiagnosticAnalyzerProvider? _lazyProvider; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public Factory(IThreadingContext threadingContext, SVsServiceProvider serviceProvider) + { + _threadingContext = threadingContext; + _serviceProvider = serviceProvider; + } + + public async Task GetOrCreateProviderAsync() + { + // the following code requires UI thread: + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + return GetOrCreateProviderOnMainThread(); + } + + public VisualStudioDiagnosticAnalyzerProvider GetOrCreateProviderOnMainThread() + { + Contract.ThrowIfFalse(_threadingContext.JoinableTaskContext.IsOnMainThread); + + if (_lazyProvider != null) + { + return _lazyProvider; + } + + var dte = (EnvDTE.DTE)_serviceProvider.GetService(typeof(EnvDTE.DTE)); + + // Microsoft.VisualStudio.ExtensionManager is non-versioned, so we need to dynamically load it, depending on the version of VS we are running on + // this will allow us to build once and deploy on different versions of VS SxS. + var vsDteVersion = Version.Parse(dte.Version.Split(' ')[0]); // DTE.Version is in the format of D[D[.D[D]]][ (?+)], so we need to split out the version part and check for uninitialized Major/Minor below + + var assembly = Assembly.Load($"Microsoft.VisualStudio.ExtensionManager, Version={(vsDteVersion.Major == -1 ? 0 : vsDteVersion.Major)}.{(vsDteVersion.Minor == -1 ? 0 : vsDteVersion.Minor)}.0.0, PublicKeyToken=b03f5f7f11d50a3a"); + var typeIExtensionContent = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.IExtensionContent"); + var type = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.SVsExtensionManager"); + var extensionManager = _serviceProvider.GetService(type); + + return _lazyProvider = new VisualStudioDiagnosticAnalyzerProvider(extensionManager, typeIExtensionContent); + } + } + } +} diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs index ce1d8d54c2928..09ae8909f51ad 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs @@ -7,6 +7,7 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host; @@ -27,19 +28,16 @@ internal partial class VisualStudioDiagnosticAnalyzerProvider internal sealed class WorkspaceEventListener : IEventListener { private readonly IAsynchronousOperationListener _listener; - private readonly IThreadingContext _threadingContext; - private readonly IServiceProvider _serviceProvider; + private readonly Factory _providerFactory; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public WorkspaceEventListener( - IThreadingContext threadingContext, - Shell.SVsServiceProvider serviceProvider, - IAsynchronousOperationListenerProvider listenerProvider) + IAsynchronousOperationListenerProvider listenerProvider, + Factory providerFactory) { - _threadingContext = threadingContext; - _serviceProvider = serviceProvider; _listener = listenerProvider.GetListener(nameof(Workspace)); + _providerFactory = providerFactory; } public void StartListening(Workspace workspace, object serviceOpt) @@ -57,11 +55,11 @@ private async Task InitializeWorkspaceAsync(ISolutionAnalyzerSetterWorkspaceServ { try { - var provider = await CreateProviderAsync().ConfigureAwait(false); + var provider = await _providerFactory.GetOrCreateProviderAsync().ConfigureAwait(false); var references = provider.GetAnalyzerReferencesInExtensions(); LogWorkspaceAnalyzerCount(references.Length); - setter.SetAnalyzerReferences(references); + setter.SetAnalyzerReferences(references.SelectAsArray(referenceAndId => (AnalyzerReference)referenceAndId.reference)); } catch (Exception e) when (FatalError.ReportAndPropagate(e, ErrorSeverity.Diagnostic)) { @@ -69,25 +67,6 @@ private async Task InitializeWorkspaceAsync(ISolutionAnalyzerSetterWorkspaceServ } } - private async Task CreateProviderAsync() - { - // the following code requires UI thread: - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); - - var dte = (EnvDTE.DTE)_serviceProvider.GetService(typeof(EnvDTE.DTE)); - - // Microsoft.VisualStudio.ExtensionManager is non-versioned, so we need to dynamically load it, depending on the version of VS we are running on - // this will allow us to build once and deploy on different versions of VS SxS. - var vsDteVersion = Version.Parse(dte.Version.Split(' ')[0]); // DTE.Version is in the format of D[D[.D[D]]][ (?+)], so we need to split out the version part and check for uninitialized Major/Minor below - - var assembly = Assembly.Load($"Microsoft.VisualStudio.ExtensionManager, Version={(vsDteVersion.Major == -1 ? 0 : vsDteVersion.Major)}.{(vsDteVersion.Minor == -1 ? 0 : vsDteVersion.Minor)}.0.0, PublicKeyToken=b03f5f7f11d50a3a"); - var typeIExtensionContent = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.IExtensionContent"); - var type = assembly.GetType("Microsoft.VisualStudio.ExtensionManager.SVsExtensionManager"); - var extensionManager = _serviceProvider.GetService(type); - - return new VisualStudioDiagnosticAnalyzerProvider(extensionManager, typeIExtensionContent); - } - private static void LogWorkspaceAnalyzerCount(int analyzerCount) { Logger.Log(FunctionId.DiagnosticAnalyzerService_Analyzers, KeyValueLogMessage.Create(m => m["AnalyzerCount"] = analyzerCount)); diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs index 4e461a9787535..326f73de45508 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs @@ -29,6 +29,8 @@ internal partial class VisualStudioDiagnosticAnalyzerProvider private readonly object _extensionManager; private readonly Type _typeIExtensionContent; + private readonly Lazy> _lazyAnalyzerReferences; + // internal for testing internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type typeIExtensionContent) { @@ -37,16 +39,19 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty _extensionManager = extensionManager; _typeIExtensionContent = typeIExtensionContent; + _lazyAnalyzerReferences = new Lazy>(GetAnalyzerReferencesImpl); } - // internal for testing - internal ImmutableArray GetAnalyzerReferencesInExtensions() + public ImmutableArray<(AnalyzerFileReference reference, string? extensionId)> GetAnalyzerReferencesInExtensions() + => _lazyAnalyzerReferences.Value; + + private ImmutableArray<(AnalyzerFileReference reference, string? extensionId)> GetAnalyzerReferencesImpl() { try { // dynamic is weird. it can't see internal type with public interface even if callee is // implementation of the public interface in internal type. so we can't use dynamic here - var _ = PooledHashSet.GetInstance(out var analyzePaths); + var _ = PooledDictionary.GetInstance(out var analyzePaths); // var enabledExtensions = extensionManager.GetEnabledExtensions(AnalyzerContentTypeName); var extensionManagerType = _extensionManager.GetType(); @@ -55,15 +60,13 @@ internal ImmutableArray GetAnalyzerReferencesInExtensions() foreach (var extension in enabledExtensions) { - // var name = extension.Header.LocalizedName; var extensionType = extension.GetType(); var extensionType_HeaderProperty = extensionType.GetRuntimeProperty("Header"); var extension_Header = extensionType_HeaderProperty.GetValue(extension); var extension_HeaderType = extension_Header.GetType(); - var extension_HeaderType_LocalizedNameProperty = extension_HeaderType.GetRuntimeProperty("LocalizedName"); - var name = extension_HeaderType_LocalizedNameProperty.GetValue(extension_Header) as string; + var extension_HeaderType_Identity = extension_HeaderType.GetRuntimeProperty("Identifier"); + var identity = extension_HeaderType_Identity.GetValue(extension_Header) as string; - // var extension_Content = extension.Content; var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content"); var extension_Content = (IEnumerable)extensionType_ContentProperty.GetValue(extension); @@ -81,7 +84,7 @@ internal ImmutableArray GetAnalyzerReferencesInExtensions() continue; } - analyzePaths.Add(assemblyPath); + analyzePaths.Add(assemblyPath, identity); } } @@ -89,7 +92,7 @@ internal ImmutableArray GetAnalyzerReferencesInExtensions() // so that we can debug it through if mandatory analyzers are missing GC.KeepAlive(enabledExtensions); - return analyzePaths.SelectAsArray(path => (AnalyzerReference)new AnalyzerFileReference(path, AnalyzerAssemblyLoader)); + return analyzePaths.SelectAsArray(pathAndId => (new AnalyzerFileReference(pathAndId.Key, AnalyzerAssemblyLoader), pathAndId.Value)); } catch (TargetInvocationException ex) when (ex.InnerException is InvalidOperationException) { @@ -99,7 +102,7 @@ internal ImmutableArray GetAnalyzerReferencesInExtensions() // // fortunately, this only happens on disposing at shutdown, so we just catch the exception and silently swallow it. // we are about to shutdown anyway. - return ImmutableArray.Empty; + return ImmutableArray<(AnalyzerFileReference, string?)>.Empty; } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs index f01b8141f9036..1c5ac582e63b6 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs @@ -5,9 +5,13 @@ using System; using System.Collections.Immutable; using System.IO; +using System.Linq; +using System.Windows.Forms; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; +using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { @@ -30,7 +34,11 @@ internal sealed class VisualStudioAnalyzer : IDisposable private AnalyzerReference? _analyzerReference; private ImmutableArray _analyzerLoadErrors = ImmutableArray.Empty; - public VisualStudioAnalyzer(string fullPath, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, ProjectId projectId, string language) + public VisualStudioAnalyzer( + string fullPath, + HostDiagnosticUpdateSource hostDiagnosticUpdateSource, + ProjectId projectId, + string language) { FullPath = fullPath; _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs index 310bd18639769..dcaa37f4dc410 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Telemetry; using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Roslyn.Utilities; @@ -26,10 +27,12 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { internal sealed partial class VisualStudioProject { + private static readonly char[] s_directorySeparator = { Path.DirectorySeparatorChar }; private static readonly ImmutableArray s_defaultMetadataReferenceProperties = ImmutableArray.Create(default(MetadataReferenceProperties)); private readonly VisualStudioWorkspaceImpl _workspace; private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; + private readonly VisualStudioDiagnosticAnalyzerProvider _vsixAnalyzerProvider; /// /// Provides dynamic source files for files added through . @@ -145,6 +148,7 @@ internal VisualStudioProject( VisualStudioWorkspaceImpl workspace, ImmutableArray> dynamicFileInfoProviders, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, + VisualStudioDiagnosticAnalyzerProvider vsixAnalyzerProvider, ProjectId id, string displayName, string language, @@ -156,6 +160,7 @@ internal VisualStudioProject( _workspace = workspace; _dynamicFileInfoProviders = dynamicFileInfoProviders; _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; + _vsixAnalyzerProvider = vsixAnalyzerProvider; Id = id; Language = language; @@ -627,6 +632,7 @@ await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges ClearAndZeroCapacity(_projectReferencesRemovedInBatch); // Analyzer reference adding... + solutionChanges.UpdateSolutionForProjectAction( Id, newSolution: solutionChanges.Solution.AddAnalyzerReferences(Id, _analyzersAddedInBatch.Select(a => a.GetReference()))); @@ -879,78 +885,107 @@ public void AddAnalyzerReference(string fullPath) using (_gate.DisposableWait()) { - if (_analyzerPathsToAnalyzers.ContainsKey(fullPath)) - { - throw new ArgumentException($"'{fullPath}' has already been added to this project.", nameof(fullPath)); - } - - // Are we adding one we just recently removed? If so, we can just keep using that one, and avoid removing - // it once we apply the batch - var analyzerPendingRemoval = _analyzersRemovedInBatch.FirstOrDefault(a => a.FullPath == fullPath); - if (analyzerPendingRemoval != null) - { - _analyzersRemovedInBatch.Remove(analyzerPendingRemoval); - _analyzerPathsToAnalyzers.Add(fullPath, analyzerPendingRemoval); - } - else + foreach (var mappedFullPath in GetMapedAnalyzerPaths(fullPath)) { - // Nope, we actually need to make a new one. - var visualStudioAnalyzer = new VisualStudioAnalyzer( - fullPath, - _hostDiagnosticUpdateSource, - Id, - Language); - - _analyzerPathsToAnalyzers.Add(fullPath, visualStudioAnalyzer); - - if (_activeBatchScopes > 0) + if (_analyzerPathsToAnalyzers.ContainsKey(mappedFullPath)) + { + throw new ArgumentException($"'{mappedFullPath}' has already been added to this project.", nameof(mappedFullPath)); + } + // Are we adding one we just recently removed? If so, we can just keep using that one, and avoid removing + // it once we apply the batch + var analyzerPendingRemoval = _analyzersRemovedInBatch.FirstOrDefault(a => a.FullPath == mappedFullPath); + if (analyzerPendingRemoval != null) { - _analyzersAddedInBatch.Add(visualStudioAnalyzer); + _analyzersRemovedInBatch.Remove(analyzerPendingRemoval); + _analyzerPathsToAnalyzers.Add(mappedFullPath, analyzerPendingRemoval); } else { - _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceAdded(Id, visualStudioAnalyzer.GetReference())); + // Nope, we actually need to make a new one. + var visualStudioAnalyzer = new VisualStudioAnalyzer( + mappedFullPath, + _hostDiagnosticUpdateSource, + Id, + Language); + + _analyzerPathsToAnalyzers.Add(mappedFullPath, visualStudioAnalyzer); + + if (_activeBatchScopes > 0) + { + _analyzersAddedInBatch.Add(visualStudioAnalyzer); + } + else + { + _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceAdded(Id, visualStudioAnalyzer.GetReference())); + } } } } } - public void RemoveAnalyzerReference(string fullPath) + public void RemoveAnalyzerReference(string fullPathX) { - if (string.IsNullOrEmpty(fullPath)) + if (string.IsNullOrEmpty(fullPathX)) { - throw new ArgumentException("message", nameof(fullPath)); + throw new ArgumentException("message", nameof(fullPathX)); } using (_gate.DisposableWait()) { - if (!_analyzerPathsToAnalyzers.TryGetValue(fullPath, out var visualStudioAnalyzer)) + foreach (var mappedFullPath in GetMapedAnalyzerPaths(fullPathX)) { - throw new ArgumentException($"'{fullPath}' is not an analyzer of this project.", nameof(fullPath)); - } + if (!_analyzerPathsToAnalyzers.TryGetValue(mappedFullPath, out var visualStudioAnalyzer)) + { + throw new ArgumentException($"'{mappedFullPath}' is not an analyzer of this project.", nameof(mappedFullPath)); + } - _analyzerPathsToAnalyzers.Remove(fullPath); + _analyzerPathsToAnalyzers.Remove(mappedFullPath); - if (_activeBatchScopes > 0) - { - // This analyzer may be one we've just added in the same batch; in that case, just don't add - // it in the first place. - if (_analyzersAddedInBatch.Remove(visualStudioAnalyzer)) + if (_activeBatchScopes > 0) { - // Nothing is holding onto this analyzer now, so get rid of it - visualStudioAnalyzer.Dispose(); + // This analyzer may be one we've just added in the same batch; in that case, just don't add + // it in the first place. + if (_analyzersAddedInBatch.Remove(visualStudioAnalyzer)) + { + // Nothing is holding onto this analyzer now, so get rid of it + visualStudioAnalyzer.Dispose(); + } + else + { + _analyzersRemovedInBatch.Add(visualStudioAnalyzer); + } } else { - _analyzersRemovedInBatch.Add(visualStudioAnalyzer); + _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceRemoved(Id, visualStudioAnalyzer.GetReference())); + + visualStudioAnalyzer.Dispose(); } } - else + } + } + + private IEnumerable GetMapedAnalyzerPaths(string fullPath) + { + if (fullPath.Split(s_directorySeparator) is [.., "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", var fileName]) + { + // Include the generator and all its dependencies shipped in VSIX, discard the generator and all dependencies in the SDK + + if (fileName == "Microsoft.NET.Sdk.Razor.SourceGenerators.dll") { - _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceRemoved(Id, visualStudioAnalyzer.GetReference())); - visualStudioAnalyzer.Dispose(); + foreach (var (vsixAnalyzer, extensionId) in _vsixAnalyzerProvider.GetAnalyzerReferencesInExtensions()) + { + if (extensionId == "Microsoft.VisualStudio.RazorExtension") + { + yield return vsixAnalyzer.FullPath; + } + } } } + else + { + yield return fullPath; + } } #endregion diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs index 42871a3177534..c7464041b465e 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.VisualStudio.LanguageServices.ExternalAccess.VSTypeScript.Api; +using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -33,6 +34,7 @@ internal sealed class VisualStudioProjectFactory : IVsTypeScriptVisualStudioProj private readonly VisualStudioWorkspaceImpl _visualStudioWorkspaceImpl; private readonly ImmutableArray> _dynamicFileInfoProviders; private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; + private readonly VisualStudioDiagnosticAnalyzerProvider.Factory _vsixAnalyzerProviderFactory; private readonly Shell.IAsyncServiceProvider _serviceProvider; [ImportingConstructor] @@ -42,12 +44,14 @@ public VisualStudioProjectFactory( VisualStudioWorkspaceImpl visualStudioWorkspaceImpl, [ImportMany] IEnumerable> fileInfoProviders, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, + VisualStudioDiagnosticAnalyzerProvider.Factory vsixAnalyzerProviderFactory, SVsServiceProvider serviceProvider) { _threadingContext = threadingContext; _visualStudioWorkspaceImpl = visualStudioWorkspaceImpl; _dynamicFileInfoProviders = fileInfoProviders.AsImmutableOrEmpty(); _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; + _vsixAnalyzerProviderFactory = vsixAnalyzerProviderFactory; _serviceProvider = (Shell.IAsyncServiceProvider)serviceProvider; } @@ -74,6 +78,8 @@ public async Task CreateAndAddToWorkspaceAsync( ? filePath : null; + var vsixAnalyzerProvider = _vsixAnalyzerProviderFactory.GetOrCreateProviderOnMainThread(); + // Following can be off the UI thread. await TaskScheduler.Default; @@ -88,6 +94,7 @@ public async Task CreateAndAddToWorkspaceAsync( _visualStudioWorkspaceImpl, _dynamicFileInfoProviders, _hostDiagnosticUpdateSource, + vsixAnalyzerProvider, id, displayName: projectSystemName, language, diff --git a/src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb b/src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb index f1cae1e1428e0..0291b1eb44726 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb @@ -18,9 +18,10 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Private ReadOnly _contentType As String Private ReadOnly _locations() As String - Public Sub New(contentType As String, ParamArray locations() As String) + Public Sub New(Optional locations As String() = Nothing, + Optional contentType As String = "Microsoft.VisualStudio.Analyzer") _contentType = contentType - _locations = locations + _locations = If(locations, Array.Empty(Of String)) End Sub Public Function GetEnabledExtensionContentLocations(contentTypeName As String) As IEnumerable(Of String) diff --git a/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb b/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb index bf2bb51780089..a552a71bb14cc 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb @@ -15,7 +15,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Sub GetAnalyzerReferencesInExtensions_Substitution() Dim extensionManager = New VisualStudioDiagnosticAnalyzerProvider( - New MockExtensionManager("Microsoft.VisualStudio.Analyzer", "$RootFolder$\test\test.dll", "$ShellFolder$\test\test.dll", "test\test.dll"), + New MockExtensionManager({"$RootFolder$\test\test.dll", "$ShellFolder$\test\test.dll", "test\test.dll"}), GetType(MockExtensionManager.MockContent)) Dim references = extensionManager.GetAnalyzerReferencesInExtensions() @@ -26,13 +26,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Path.Combine(TempRoot.Root, "ResolvedShellFolder\test\test.dll"), Path.Combine(TempRoot.Root, "InstallPath\test\test.dll") }, - references.Select(Function(reference) reference.FullPath)) + references.Select(Function(referenceAndId) referenceAndId.reference.FullPath)) End Sub Public Sub GetAnalyzerReferencesInExtensions() Dim extensionManager = New VisualStudioDiagnosticAnalyzerProvider( - New MockExtensionManager("Microsoft.VisualStudio.Analyzer", "installPath1", "installPath2", "installPath3"), + New MockExtensionManager({"installPath1", "installPath2", "installPath3"}), GetType(MockExtensionManager.MockContent)) Dim references = extensionManager.GetAnalyzerReferencesInExtensions() @@ -43,7 +43,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Path.Combine(TempRoot.Root, "InstallPath\installPath2"), Path.Combine(TempRoot.Root, "InstallPath\installPath3") }, - references.Select(Function(reference) reference.FullPath)) + references.Select(Function(referenceAndId) referenceAndId.reference.FullPath)) End Sub diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb index 6d77b72c7a40f..981acc745bbeb 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb @@ -11,8 +11,10 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Imports Roslyn.Test.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim From 54e2fe8b991257af25739a7e4308d03e833f0485 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Sep 2022 15:33:07 -0700 Subject: [PATCH 2/9] Rename --- .../Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs index 326f73de45508..7cc0c7da625a5 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs @@ -64,8 +64,8 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty var extensionType_HeaderProperty = extensionType.GetRuntimeProperty("Header"); var extension_Header = extensionType_HeaderProperty.GetValue(extension); var extension_HeaderType = extension_Header.GetType(); - var extension_HeaderType_Identity = extension_HeaderType.GetRuntimeProperty("Identifier"); - var identity = extension_HeaderType_Identity.GetValue(extension_Header) as string; + var extension_HeaderType_Identifier = extension_HeaderType.GetRuntimeProperty("Identifier"); + var identifier = extension_HeaderType_Identifier.GetValue(extension_Header) as string; var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content"); var extension_Content = (IEnumerable)extensionType_ContentProperty.GetValue(extension); @@ -84,7 +84,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty continue; } - analyzePaths.Add(assemblyPath, identity); + analyzePaths.Add(assemblyPath, identifier); } } From 061197b7dcd85bdb00a0049743cd1945ceb9c4a6 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Sep 2022 15:35:56 -0700 Subject: [PATCH 3/9] Cleanup --- .../Core/Def/ProjectSystem/VisualStudioAnalyzer.cs | 10 +--------- .../Core/Def/ProjectSystem/VisualStudioProject.cs | 1 - .../ProjectSystemShim/VisualStudioAnalyzerTests.vb | 2 -- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs index 1c5ac582e63b6..f01b8141f9036 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioAnalyzer.cs @@ -5,13 +5,9 @@ using System; using System.Collections.Immutable; using System.IO; -using System.Linq; -using System.Windows.Forms; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem { @@ -34,11 +30,7 @@ internal sealed class VisualStudioAnalyzer : IDisposable private AnalyzerReference? _analyzerReference; private ImmutableArray _analyzerLoadErrors = ImmutableArray.Empty; - public VisualStudioAnalyzer( - string fullPath, - HostDiagnosticUpdateSource hostDiagnosticUpdateSource, - ProjectId projectId, - string language) + public VisualStudioAnalyzer(string fullPath, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, ProjectId projectId, string language) { FullPath = fullPath; _hostDiagnosticUpdateSource = hostDiagnosticUpdateSource; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs index dcaa37f4dc410..1952bff099132 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs @@ -632,7 +632,6 @@ await _workspace.ApplyBatchChangeToWorkspaceMaybeAsync(useAsync, solutionChanges ClearAndZeroCapacity(_projectReferencesRemovedInBatch); // Analyzer reference adding... - solutionChanges.UpdateSolutionForProjectAction( Id, newSolution: solutionChanges.Solution.AddAnalyzerReferences(Id, _analyzersAddedInBatch.Select(a => a.GetReference()))); diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb index 981acc745bbeb..6d77b72c7a40f 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioAnalyzerTests.vb @@ -11,10 +11,8 @@ Imports Microsoft.CodeAnalysis.Editor.UnitTests.Diagnostics Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces Imports Microsoft.CodeAnalysis.Options Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList -Imports Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Imports Roslyn.Test.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim From f61d0c4189c67c8566e5caf2ff35f70836b828e6 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Sep 2022 17:12:45 -0700 Subject: [PATCH 4/9] Feedback --- .../Core/Def/ProjectSystem/VisualStudioProject.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs index 1952bff099132..063f025e89fc2 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs @@ -922,16 +922,16 @@ public void AddAnalyzerReference(string fullPath) } } - public void RemoveAnalyzerReference(string fullPathX) + public void RemoveAnalyzerReference(string fullPath) { - if (string.IsNullOrEmpty(fullPathX)) + if (string.IsNullOrEmpty(fullPath)) { - throw new ArgumentException("message", nameof(fullPathX)); + throw new ArgumentException("message", nameof(fullPath)); } using (_gate.DisposableWait()) { - foreach (var mappedFullPath in GetMapedAnalyzerPaths(fullPathX)) + foreach (var mappedFullPath in GetMapedAnalyzerPaths(fullPath)) { if (!_analyzerPathsToAnalyzers.TryGetValue(mappedFullPath, out var visualStudioAnalyzer)) { @@ -966,7 +966,10 @@ public void RemoveAnalyzerReference(string fullPathX) private IEnumerable GetMapedAnalyzerPaths(string fullPath) { - if (fullPath.Split(s_directorySeparator) is [.., "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", var fileName]) + if (fullPath.Split(s_directorySeparator) is [.., var dir1, var dir2, var dir3, var fileName] && + dir1.Equals("Sdks", StringComparison.OrdinalIgnoreCase) && + dir2.Equals("Microsoft.NET.Sdk.Razor", StringComparison.OrdinalIgnoreCase) && + dir3.Equals("source-generators", StringComparison.OrdinalIgnoreCase)) { // Include the generator and all its dependencies shipped in VSIX, discard the generator and all dependencies in the SDK From 076b515844aec64fa12d741f6f9d8fc3fdc2822b Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Sep 2022 17:13:25 -0700 Subject: [PATCH 5/9] Feedbakc --- src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs index 063f025e89fc2..2b8f7a477a713 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs @@ -973,7 +973,7 @@ private IEnumerable GetMapedAnalyzerPaths(string fullPath) { // Include the generator and all its dependencies shipped in VSIX, discard the generator and all dependencies in the SDK - if (fileName == "Microsoft.NET.Sdk.Razor.SourceGenerators.dll") + if (fileName.Equals("Microsoft.NET.Sdk.Razor.SourceGenerators.dll", StringComparison.OrdinalIgnoreCase)) { foreach (var (vsixAnalyzer, extensionId) in _vsixAnalyzerProvider.GetAnalyzerReferencesInExtensions()) { From b94199e0921d3f5eff8014b183bcf8f99c869748 Mon Sep 17 00:00:00 2001 From: tmat Date: Mon, 12 Sep 2022 19:25:47 -0700 Subject: [PATCH 6/9] Typo --- .../Core/Def/ProjectSystem/VisualStudioProject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs index 2b8f7a477a713..c1f95ed1c399b 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs @@ -884,7 +884,7 @@ public void AddAnalyzerReference(string fullPath) using (_gate.DisposableWait()) { - foreach (var mappedFullPath in GetMapedAnalyzerPaths(fullPath)) + foreach (var mappedFullPath in GetMappedAnalyzerPaths(fullPath)) { if (_analyzerPathsToAnalyzers.ContainsKey(mappedFullPath)) { @@ -931,7 +931,7 @@ public void RemoveAnalyzerReference(string fullPath) using (_gate.DisposableWait()) { - foreach (var mappedFullPath in GetMapedAnalyzerPaths(fullPath)) + foreach (var mappedFullPath in GetMappedAnalyzerPaths(fullPath)) { if (!_analyzerPathsToAnalyzers.TryGetValue(mappedFullPath, out var visualStudioAnalyzer)) { @@ -964,7 +964,7 @@ public void RemoveAnalyzerReference(string fullPath) } } - private IEnumerable GetMapedAnalyzerPaths(string fullPath) + private IEnumerable GetMappedAnalyzerPaths(string fullPath) { if (fullPath.Split(s_directorySeparator) is [.., var dir1, var dir2, var dir3, var fileName] && dir1.Equals("Sdks", StringComparison.OrdinalIgnoreCase) && From e1f6fdd4b4f30baa770731f8c2ba5038cdcf3b95 Mon Sep 17 00:00:00 2001 From: tmat Date: Tue, 13 Sep 2022 12:05:37 -0700 Subject: [PATCH 7/9] Fix tests --- ...StudioDiagnosticAnalyzerProviderFactory.cs | 14 +++++++++ ...tudioDiagnosticAnalyzerProvider.Factory.cs | 2 +- ...AnalyzerProvider.WorkspaceEventListener.cs | 4 +-- .../VisualStudioProjectFactory.cs | 4 +-- .../MockExtensionManager.vb | 3 -- ...StudioDiagnosticAnalyzerProviderFactory.vb | 30 +++++++++++++++++++ .../Framework/TestEnvironment.vb | 3 ++ 7 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs rename src/VisualStudio/{Core/Test/Diagnostics => TestUtilities2}/MockExtensionManager.vb (97%) create mode 100644 src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb diff --git a/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs b/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs new file mode 100644 index 0000000000000..a1d9729270d11 --- /dev/null +++ b/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs @@ -0,0 +1,14 @@ +// 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.Tasks; + +namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics +{ + internal interface IVisualStudioDiagnosticAnalyzerProviderFactory + { + Task GetOrCreateProviderAsync(); + VisualStudioDiagnosticAnalyzerProvider GetOrCreateProviderOnMainThread(); + } +} diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs index b39e109faf709..1446e123af24e 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs @@ -15,7 +15,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics { internal partial class VisualStudioDiagnosticAnalyzerProvider { - [Export(typeof(Factory)), Shared] + [Export(typeof(IVisualStudioDiagnosticAnalyzerProviderFactory)), Shared] internal sealed class Factory { private readonly IThreadingContext _threadingContext; diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs index 09ae8909f51ad..bf764fa14a810 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs @@ -28,13 +28,13 @@ internal partial class VisualStudioDiagnosticAnalyzerProvider internal sealed class WorkspaceEventListener : IEventListener { private readonly IAsynchronousOperationListener _listener; - private readonly Factory _providerFactory; + private readonly IVisualStudioDiagnosticAnalyzerProviderFactory _providerFactory; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public WorkspaceEventListener( IAsynchronousOperationListenerProvider listenerProvider, - Factory providerFactory) + IVisualStudioDiagnosticAnalyzerProviderFactory providerFactory) { _listener = listenerProvider.GetListener(nameof(Workspace)); _providerFactory = providerFactory; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs index c7464041b465e..b8cd0d01d74a3 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs @@ -34,7 +34,7 @@ internal sealed class VisualStudioProjectFactory : IVsTypeScriptVisualStudioProj private readonly VisualStudioWorkspaceImpl _visualStudioWorkspaceImpl; private readonly ImmutableArray> _dynamicFileInfoProviders; private readonly HostDiagnosticUpdateSource _hostDiagnosticUpdateSource; - private readonly VisualStudioDiagnosticAnalyzerProvider.Factory _vsixAnalyzerProviderFactory; + private readonly IVisualStudioDiagnosticAnalyzerProviderFactory _vsixAnalyzerProviderFactory; private readonly Shell.IAsyncServiceProvider _serviceProvider; [ImportingConstructor] @@ -44,7 +44,7 @@ public VisualStudioProjectFactory( VisualStudioWorkspaceImpl visualStudioWorkspaceImpl, [ImportMany] IEnumerable> fileInfoProviders, HostDiagnosticUpdateSource hostDiagnosticUpdateSource, - VisualStudioDiagnosticAnalyzerProvider.Factory vsixAnalyzerProviderFactory, + IVisualStudioDiagnosticAnalyzerProviderFactory vsixAnalyzerProviderFactory, SVsServiceProvider serviceProvider) { _threadingContext = threadingContext; diff --git a/src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb b/src/VisualStudio/TestUtilities2/MockExtensionManager.vb similarity index 97% rename from src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb rename to src/VisualStudio/TestUtilities2/MockExtensionManager.vb index 0291b1eb44726..7ce8a2c46197e 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb +++ b/src/VisualStudio/TestUtilities2/MockExtensionManager.vb @@ -2,19 +2,16 @@ ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. -Imports System.ComponentModel Imports System.Globalization Imports System.IO Imports System.Xml Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Test.Utilities -Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics Imports Moq Imports Roslyn.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Class MockExtensionManager - Private ReadOnly _contentType As String Private ReadOnly _locations() As String diff --git a/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb b/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb new file mode 100644 index 0000000000000..c74dc78ee09f1 --- /dev/null +++ b/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb @@ -0,0 +1,30 @@ +' 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. + +Imports System.Composition +Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics + +Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics + + + Friend Class MockVisualStudioDiagnosticAnalyzerProviderFactory + Implements IVisualStudioDiagnosticAnalyzerProviderFactory + + + + Public Sub New() + End Sub + + Public Function GetOrCreateProviderAsync() As Task(Of VisualStudioDiagnosticAnalyzerProvider) Implements IVisualStudioDiagnosticAnalyzerProviderFactory.GetOrCreateProviderAsync + Return Task.FromResult(GetOrCreateProviderOnMainThread()) + End Function + + Public Function GetOrCreateProviderOnMainThread() As VisualStudioDiagnosticAnalyzerProvider Implements IVisualStudioDiagnosticAnalyzerProviderFactory.GetOrCreateProviderOnMainThread + Return New VisualStudioDiagnosticAnalyzerProvider( + New MockExtensionManager(Array.Empty(Of String)()), + GetType(MockExtensionManager.MockContent)) + End Function + End Class +End Namespace diff --git a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb index 89bede4b5d7c8..cf93d91c88192 100644 --- a/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb +++ b/src/VisualStudio/TestUtilities2/ProjectSystemShim/Framework/TestEnvironment.vb @@ -19,12 +19,14 @@ Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.Composition Imports Microsoft.VisualStudio.LanguageServices.Implementation Imports Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel +Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics Imports Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.CPS Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.Legacy Imports Microsoft.VisualStudio.LanguageServices.Implementation.TaskList Imports Microsoft.VisualStudio.LanguageServices.UnitTests.CodeModel +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Imports Microsoft.VisualStudio.Shell Imports Microsoft.VisualStudio.Shell.Interop @@ -57,6 +59,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr GetType(MockVisualStudioWorkspace), GetType(MetadataReferences.FileWatchedPortableExecutableReferenceFactory), GetType(VisualStudioProjectFactory), + GetType(MockVisualStudioDiagnosticAnalyzerProviderFactory), GetType(MockServiceProvider), GetType(SolutionEventsBatchScopeCreator), GetType(ProjectCodeModelFactory), From d292fb1c738af765b9dabebc82e3fd2af5441a71 Mon Sep 17 00:00:00 2001 From: tmat Date: Wed, 14 Sep 2022 10:56:15 -0700 Subject: [PATCH 8/9] Feedback --- ...StudioDiagnosticAnalyzerProviderFactory.cs | 7 +- ...tudioDiagnosticAnalyzerProvider.Factory.cs | 9 +- ...AnalyzerProvider.WorkspaceEventListener.cs | 3 +- .../VisualStudioDiagnosticAnalyzerProvider.cs | 19 ++-- .../Def/ProjectSystem/VisualStudioProject.cs | 62 +++++++----- .../VisualStudioProjectFactory.cs | 2 +- ...alStudioDiagnosticAnalyzerProviderTests.vb | 4 +- .../AnalyzerReferenceTests.vb | 97 +++++++++++++++++++ .../TestUtilities2/MockExtensionManager.vb | 41 ++++---- ...StudioDiagnosticAnalyzerProviderFactory.vb | 13 +-- 10 files changed, 184 insertions(+), 73 deletions(-) diff --git a/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs b/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs index a1d9729270d11..3e58c6a824b5f 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs @@ -2,13 +2,16 @@ // 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; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics { + /// + /// Abstraction for testing purposes. + /// internal interface IVisualStudioDiagnosticAnalyzerProviderFactory { - Task GetOrCreateProviderAsync(); - VisualStudioDiagnosticAnalyzerProvider GetOrCreateProviderOnMainThread(); + Task GetOrCreateProviderAsync(CancellationToken cancellationToken); } } diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs index 1446e123af24e..bdc6177c05d9e 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs @@ -10,13 +10,14 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.VisualStudio.Shell; +using System.Threading; namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics { internal partial class VisualStudioDiagnosticAnalyzerProvider { [Export(typeof(IVisualStudioDiagnosticAnalyzerProviderFactory)), Shared] - internal sealed class Factory + internal sealed class Factory : IVisualStudioDiagnosticAnalyzerProviderFactory { private readonly IThreadingContext _threadingContext; private readonly IServiceProvider _serviceProvider; @@ -31,14 +32,14 @@ public Factory(IThreadingContext threadingContext, SVsServiceProvider servicePro _serviceProvider = serviceProvider; } - public async Task GetOrCreateProviderAsync() + public async Task GetOrCreateProviderAsync(CancellationToken cancellationToken) { // the following code requires UI thread: - await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(); + await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); return GetOrCreateProviderOnMainThread(); } - public VisualStudioDiagnosticAnalyzerProvider GetOrCreateProviderOnMainThread() + private VisualStudioDiagnosticAnalyzerProvider GetOrCreateProviderOnMainThread() { Contract.ThrowIfFalse(_threadingContext.JoinableTaskContext.IsOnMainThread); diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs index bf764fa14a810..f7ba92198a7ca 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs @@ -5,6 +5,7 @@ using System; using System.Composition; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; @@ -55,7 +56,7 @@ private async Task InitializeWorkspaceAsync(ISolutionAnalyzerSetterWorkspaceServ { try { - var provider = await _providerFactory.GetOrCreateProviderAsync().ConfigureAwait(false); + var provider = await _providerFactory.GetOrCreateProviderAsync(CancellationToken.None).ConfigureAwait(false); var references = provider.GetAnalyzerReferencesInExtensions(); LogWorkspaceAnalyzerCount(references.Length); diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs index 7cc0c7da625a5..3d285f6330263 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.cs @@ -29,7 +29,7 @@ internal partial class VisualStudioDiagnosticAnalyzerProvider private readonly object _extensionManager; private readonly Type _typeIExtensionContent; - private readonly Lazy> _lazyAnalyzerReferences; + private readonly Lazy> _lazyAnalyzerReferences; // internal for testing internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type typeIExtensionContent) @@ -39,19 +39,19 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty _extensionManager = extensionManager; _typeIExtensionContent = typeIExtensionContent; - _lazyAnalyzerReferences = new Lazy>(GetAnalyzerReferencesImpl); + _lazyAnalyzerReferences = new Lazy>(GetAnalyzerReferencesImpl); } - public ImmutableArray<(AnalyzerFileReference reference, string? extensionId)> GetAnalyzerReferencesInExtensions() + public ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesInExtensions() => _lazyAnalyzerReferences.Value; - private ImmutableArray<(AnalyzerFileReference reference, string? extensionId)> GetAnalyzerReferencesImpl() + private ImmutableArray<(AnalyzerFileReference reference, string extensionId)> GetAnalyzerReferencesImpl() { try { // dynamic is weird. it can't see internal type with public interface even if callee is // implementation of the public interface in internal type. so we can't use dynamic here - var _ = PooledDictionary.GetInstance(out var analyzePaths); + var _ = PooledDictionary.GetInstance(out var analyzePaths); // var enabledExtensions = extensionManager.GetEnabledExtensions(AnalyzerContentTypeName); var extensionManagerType = _extensionManager.GetType(); @@ -65,7 +65,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty var extension_Header = extensionType_HeaderProperty.GetValue(extension); var extension_HeaderType = extension_Header.GetType(); var extension_HeaderType_Identifier = extension_HeaderType.GetRuntimeProperty("Identifier"); - var identifier = extension_HeaderType_Identifier.GetValue(extension_Header) as string; + var identifier = (string)extension_HeaderType_Identifier.GetValue(extension_Header); var extensionType_ContentProperty = extensionType.GetRuntimeProperty("Content"); var extension_Content = (IEnumerable)extensionType_ContentProperty.GetValue(extension); @@ -84,7 +84,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty continue; } - analyzePaths.Add(assemblyPath, identifier); + analyzePaths.Add(new AnalyzerFileReference(assemblyPath, AnalyzerAssemblyLoader), identifier); } } @@ -92,7 +92,8 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty // so that we can debug it through if mandatory analyzers are missing GC.KeepAlive(enabledExtensions); - return analyzePaths.SelectAsArray(pathAndId => (new AnalyzerFileReference(pathAndId.Key, AnalyzerAssemblyLoader), pathAndId.Value)); + // Order for deterministic result. + return analyzePaths.OrderBy((x, y) => string.CompareOrdinal(x.Key.FullPath, y.Key.FullPath)).SelectAsArray(entry => (entry.Key, entry.Value)); } catch (TargetInvocationException ex) when (ex.InnerException is InvalidOperationException) { @@ -102,7 +103,7 @@ internal VisualStudioDiagnosticAnalyzerProvider(object extensionManager, Type ty // // fortunately, this only happens on disposing at shutdown, so we just catch the exception and silently swallow it. // we are about to shutdown anyway. - return ImmutableArray<(AnalyzerFileReference, string?)>.Empty; + return ImmutableArray<(AnalyzerFileReference, string)>.Empty; } } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs index c1f95ed1c399b..b2c5e26376030 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Collections; +using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; @@ -882,14 +883,21 @@ public void AddAnalyzerReference(string fullPath) { CompilerPathUtilities.RequireAbsolutePath(fullPath, nameof(fullPath)); + var mappedPaths = GetMappedAnalyzerPaths(fullPath); + using (_gate.DisposableWait()) { - foreach (var mappedFullPath in GetMappedAnalyzerPaths(fullPath)) + // check all mapped paths first, so that all analyzers are either added or not + foreach (var mappedFullPath in mappedPaths) { if (_analyzerPathsToAnalyzers.ContainsKey(mappedFullPath)) { - throw new ArgumentException($"'{mappedFullPath}' has already been added to this project.", nameof(mappedFullPath)); + throw new ArgumentException($"'{fullPath}' has already been added to this project.", nameof(fullPath)); } + } + + foreach (var mappedFullPath in mappedPaths) + { // Are we adding one we just recently removed? If so, we can just keep using that one, and avoid removing // it once we apply the batch var analyzerPendingRemoval = _analyzersRemovedInBatch.FirstOrDefault(a => a.FullPath == mappedFullPath); @@ -929,14 +937,22 @@ public void RemoveAnalyzerReference(string fullPath) throw new ArgumentException("message", nameof(fullPath)); } + var mappedPaths = GetMappedAnalyzerPaths(fullPath); + using (_gate.DisposableWait()) { - foreach (var mappedFullPath in GetMappedAnalyzerPaths(fullPath)) + // check all mapped paths first, so that all analyzers are either removed or not + foreach (var mappedFullPath in mappedPaths) { - if (!_analyzerPathsToAnalyzers.TryGetValue(mappedFullPath, out var visualStudioAnalyzer)) + if (!_analyzerPathsToAnalyzers.ContainsKey(mappedFullPath)) { - throw new ArgumentException($"'{mappedFullPath}' is not an analyzer of this project.", nameof(mappedFullPath)); + throw new ArgumentException($"'{fullPath}' is not an analyzer of this project.", nameof(fullPath)); } + } + + foreach (var mappedFullPath in mappedPaths) + { + var visualStudioAnalyzer = _analyzerPathsToAnalyzers[mappedFullPath]; _analyzerPathsToAnalyzers.Remove(mappedFullPath); @@ -964,30 +980,28 @@ public void RemoveAnalyzerReference(string fullPath) } } - private IEnumerable GetMappedAnalyzerPaths(string fullPath) + private const string RazorVsixExtensionId = "Microsoft.VisualStudio.RazorExtension"; + private static readonly string s_razorSourceGeneratorSdkDirectory = Path.Combine("Sdks", "Microsoft.NET.Sdk.Razor", "source-generators") + PathUtilities.DirectorySeparatorStr; + private static readonly string s_razorSourceGeneratorMainAssemblyRootedFileName = PathUtilities.DirectorySeparatorStr + "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"; + + private OneOrMany GetMappedAnalyzerPaths(string fullPath) { - if (fullPath.Split(s_directorySeparator) is [.., var dir1, var dir2, var dir3, var fileName] && - dir1.Equals("Sdks", StringComparison.OrdinalIgnoreCase) && - dir2.Equals("Microsoft.NET.Sdk.Razor", StringComparison.OrdinalIgnoreCase) && - dir3.Equals("source-generators", StringComparison.OrdinalIgnoreCase)) + // Map all files in the SDK directory that contains the Razor source generator to source generator files loaded from VSIX. + // Include the generator and all its dependencies shipped in VSIX, discard the generator and all dependencies in the SDK + if (fullPath.LastIndexOf(s_razorSourceGeneratorSdkDirectory, StringComparison.OrdinalIgnoreCase) + s_razorSourceGeneratorSdkDirectory.Length - 1 == + fullPath.LastIndexOf(Path.DirectorySeparatorChar)) { - // Include the generator and all its dependencies shipped in VSIX, discard the generator and all dependencies in the SDK - - if (fileName.Equals("Microsoft.NET.Sdk.Razor.SourceGenerators.dll", StringComparison.OrdinalIgnoreCase)) + if (fullPath.EndsWith(s_razorSourceGeneratorMainAssemblyRootedFileName, StringComparison.OrdinalIgnoreCase)) { - foreach (var (vsixAnalyzer, extensionId) in _vsixAnalyzerProvider.GetAnalyzerReferencesInExtensions()) - { - if (extensionId == "Microsoft.VisualStudio.RazorExtension") - { - yield return vsixAnalyzer.FullPath; - } - } + return OneOrMany.Create(_vsixAnalyzerProvider.GetAnalyzerReferencesInExtensions().SelectAsArray( + predicate: item => item.extensionId == RazorVsixExtensionId, + selector: item => item.reference.FullPath)); } + + return OneOrMany.Create(ImmutableArray.Empty); } - else - { - yield return fullPath; - } + + return OneOrMany.Create(fullPath); } #endregion diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs index b8cd0d01d74a3..3b862e404dfac 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs @@ -78,7 +78,7 @@ public async Task CreateAndAddToWorkspaceAsync( ? filePath : null; - var vsixAnalyzerProvider = _vsixAnalyzerProviderFactory.GetOrCreateProviderOnMainThread(); + var vsixAnalyzerProvider = await _vsixAnalyzerProviderFactory.GetOrCreateProviderAsync(cancellationToken).ConfigureAwait(false); // Following can be off the UI thread. await TaskScheduler.Default; diff --git a/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb b/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb index a552a71bb14cc..b2d7872a6bcb9 100644 --- a/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb +++ b/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb @@ -15,7 +15,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Sub GetAnalyzerReferencesInExtensions_Substitution() Dim extensionManager = New VisualStudioDiagnosticAnalyzerProvider( - New MockExtensionManager({"$RootFolder$\test\test.dll", "$ShellFolder$\test\test.dll", "test\test.dll"}), + New MockExtensionManager({({"$RootFolder$\test\test.dll", "$ShellFolder$\test\test.dll", "test\test.dll"}, "Vsix")}), GetType(MockExtensionManager.MockContent)) Dim references = extensionManager.GetAnalyzerReferencesInExtensions() @@ -32,7 +32,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Sub GetAnalyzerReferencesInExtensions() Dim extensionManager = New VisualStudioDiagnosticAnalyzerProvider( - New MockExtensionManager({"installPath1", "installPath2", "installPath3"}), + New MockExtensionManager({({"installPath1", "installPath2", "installPath3"}, "Vsix")}), GetType(MockExtensionManager.MockContent)) Dim references = extensionManager.GetAnalyzerReferencesInExtensions() diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb index f4c412bebb0f3..a47583d7e3932 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb @@ -3,11 +3,14 @@ ' See the LICENSE file in the project root for more information. Imports System.Collections.Immutable +Imports System.IO Imports System.Threading Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.[Shared].TestHooks Imports Microsoft.CodeAnalysis.Test.Utilities +Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics +Imports Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework Imports Roslyn.Test.Utilities @@ -108,6 +111,100 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim End Using End Function + + Public Async Function RazorSourceGenerator() As Task + Using environment = New TestEnvironment() + Dim providerFactory = DirectCast(environment.ExportProvider.GetExportedValue(Of IVisualStudioDiagnosticAnalyzerProviderFactory), MockVisualStudioDiagnosticAnalyzerProviderFactory) + providerFactory.Extensions = + { + ({ + Path.Combine(TempRoot.Root, "RazorVsix", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"), + Path.Combine(TempRoot.Root, "RazorVsix", "VsixDependency1.dll"), + Path.Combine(TempRoot.Root, "RazorVsix", "VsixDependency2.dll") + }, + "Microsoft.VisualStudio.RazorExtension"), + ({ + Path.Combine(TempRoot.Root, "File.dll") + }, + "AnotherExtension") + } + + Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( + "Project", LanguageNames.CSharp, CancellationToken.None) + + ' adding just Razor dependency and not the main source generator is a no-op + Using project.CreateBatchScope() + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) + End Using + + Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences) + + ' removing just Razor dependency and not the main source generator is a no-op + Using project.CreateBatchScope() + project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) + End Using + + Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences) + + ' add Razor source generator and a couple more other analyzer filess: + Using project.CreateBatchScope() + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Some other directory", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Dir", "File.dll")) + End Using + + AssertEx.Equal( + { + Path.Combine(TempRoot.Root, "RazorVsix", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"), + Path.Combine(TempRoot.Root, "RazorVsix", "VsixDependency1.dll"), + Path.Combine(TempRoot.Root, "RazorVsix", "VsixDependency2.dll"), + Path.Combine(TempRoot.Root, "Some other directory", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"), + Path.Combine(TempRoot.Root, "Dir", "File.dll") + }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) + + ' add Razor source generator again: + Using project.CreateBatchScope() + Assert.Throws(Of ArgumentException)( + "fullPath", + Sub() project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"))) + End Using + + AssertEx.Equal( + { + Path.Combine(TempRoot.Root, "RazorVsix", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"), + Path.Combine(TempRoot.Root, "RazorVsix", "VsixDependency1.dll"), + Path.Combine(TempRoot.Root, "RazorVsix", "VsixDependency2.dll"), + Path.Combine(TempRoot.Root, "Some other directory", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"), + Path.Combine(TempRoot.Root, "Dir", "File.dll") + }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) + + ' remove: + Using project.CreateBatchScope() + project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) + project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) + project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Some other directory", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) + End Using + + AssertEx.Equal( + { + Path.Combine(TempRoot.Root, "Dir", "File.dll") + }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) + + ' remove again: + Using project.CreateBatchScope() + Assert.Throws(Of ArgumentException)( + "fullPath", + Sub() project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"))) + End Using + + AssertEx.Equal( + { + Path.Combine(TempRoot.Root, "Dir", "File.dll") + }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) + End Using + End Function + Private Shared Async Function GetDiagnostics(environment As TestEnvironment) As Task(Of ImmutableArray(Of DiagnosticData)) ' Wait for diagnostics to be updated asynchronously Dim waiter = environment.ExportProvider.GetExportedValue(Of AsynchronousOperationListenerProvider).GetWaiter(FeatureAttribute.DiagnosticService) diff --git a/src/VisualStudio/TestUtilities2/MockExtensionManager.vb b/src/VisualStudio/TestUtilities2/MockExtensionManager.vb index 7ce8a2c46197e..debf644c7f18b 100644 --- a/src/VisualStudio/TestUtilities2/MockExtensionManager.vb +++ b/src/VisualStudio/TestUtilities2/MockExtensionManager.vb @@ -13,31 +13,27 @@ Imports Roslyn.Utilities Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public Class MockExtensionManager Private ReadOnly _contentType As String - Private ReadOnly _locations() As String + Private ReadOnly _extensions As (Paths As String(), Id As String)() - Public Sub New(Optional locations As String() = Nothing, + Public Sub New(Optional extensions As (Paths As String(), Id As String)() = Nothing, Optional contentType As String = "Microsoft.VisualStudio.Analyzer") _contentType = contentType - _locations = If(locations, Array.Empty(Of String)) + _extensions = If(extensions, Array.Empty(Of (Paths As String(), Id As String))) End Sub - Public Function GetEnabledExtensionContentLocations(contentTypeName As String) As IEnumerable(Of String) - Assert.Equal(_contentType, contentTypeName) - Return _locations - End Function - Public Iterator Function GetEnabledExtensions(contentTypeName As String) As IEnumerable(Of Object) Assert.Equal(_contentType, contentTypeName) - For Each location In _locations - Dim installedExtensionMock As New Mock(Of IMockInstalledExtension)(MockBehavior.Strict) + For Each extension In _extensions + For Each extensionPath In extension.Paths + Dim installedExtensionMock As New Mock(Of IMockInstalledExtension)(MockBehavior.Strict) - installedExtensionMock.SetupGet(Function(m) m.Content).Returns( + installedExtensionMock.SetupGet(Function(m) m.Content).Returns( New MockContent() { - New MockContent(_contentType, location) + New MockContent(_contentType, extensionPath) }) - installedExtensionMock.Setup(Function(m) m.GetContentLocation(It.IsAny(Of MockContent))).Returns( + installedExtensionMock.Setup(Function(m) m.GetContentLocation(It.IsAny(Of MockContent))).Returns( Function(content As MockContent) If content.RelativePath.IndexOf("$RootFolder$") >= 0 Then Return content.RelativePath.Replace("$RootFolder$", Path.Combine(TempRoot.Root, "ResolvedRootFolder")) @@ -48,12 +44,13 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics End If End Function) - Dim headerMock As New Mock(Of IMockHeader)(MockBehavior.Strict) - headerMock.SetupGet(Function(h) h.LocalizedName).Returns("Vsix") + Dim headerMock As New Mock(Of IMockHeader)(MockBehavior.Strict) + headerMock.SetupGet(Function(h) h.Identifier).Returns(extension.Id) - installedExtensionMock.SetupGet(Function(m) m.Header).Returns(headerMock.Object) + installedExtensionMock.SetupGet(Function(m) m.Header).Returns(headerMock.Object) - Yield installedExtensionMock.Object + Yield installedExtensionMock.Object + Next Next End Function @@ -64,16 +61,16 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics End Interface Public Interface IMockHeader - ReadOnly Property LocalizedName As String + ReadOnly Property Identifier As String End Interface Public Class MockContent Private ReadOnly _contentType As String - Private ReadOnly _location As String + Private ReadOnly _path As String - Public Sub New(contentType As String, location As String) + Public Sub New(contentType As String, path As String) _contentType = contentType - _location = location + _path = path End Sub Public ReadOnly Property ContentTypeName As String @@ -84,7 +81,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Public ReadOnly Property RelativePath As String Get - Return _location + Return _path End Get End Property End Class diff --git a/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb b/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb index c74dc78ee09f1..2bca62d09f7d7 100644 --- a/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb +++ b/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb @@ -3,6 +3,7 @@ ' See the LICENSE file in the project root for more information. Imports System.Composition +Imports System.Threading Imports Microsoft.CodeAnalysis.Host.Mef Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics @@ -12,19 +13,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Diagnostics Friend Class MockVisualStudioDiagnosticAnalyzerProviderFactory Implements IVisualStudioDiagnosticAnalyzerProviderFactory + Public Property Extensions As (Paths As String(), Id As String)() = Array.Empty(Of (Paths As String(), Id As String))() + Public Sub New() End Sub - Public Function GetOrCreateProviderAsync() As Task(Of VisualStudioDiagnosticAnalyzerProvider) Implements IVisualStudioDiagnosticAnalyzerProviderFactory.GetOrCreateProviderAsync - Return Task.FromResult(GetOrCreateProviderOnMainThread()) - End Function - - Public Function GetOrCreateProviderOnMainThread() As VisualStudioDiagnosticAnalyzerProvider Implements IVisualStudioDiagnosticAnalyzerProviderFactory.GetOrCreateProviderOnMainThread - Return New VisualStudioDiagnosticAnalyzerProvider( - New MockExtensionManager(Array.Empty(Of String)()), - GetType(MockExtensionManager.MockContent)) + Public Function GetOrCreateProviderAsync(cancellationToken As CancellationToken) As Task(Of VisualStudioDiagnosticAnalyzerProvider) Implements IVisualStudioDiagnosticAnalyzerProviderFactory.GetOrCreateProviderAsync + Return Task.FromResult(New VisualStudioDiagnosticAnalyzerProvider(New MockExtensionManager(Extensions), GetType(MockExtensionManager.MockContent))) End Function End Class End Namespace From f0f31f1acc8d565c232755a696ca37a50722a6f4 Mon Sep 17 00:00:00 2001 From: tmat Date: Tue, 20 Sep 2022 11:57:38 -0700 Subject: [PATCH 9/9] Feedback --- ...tudioDiagnosticAnalyzerProvider.Factory.cs | 6 --- .../AnalyzerReferenceTests.vb | 42 +++++++------------ 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs index bdc6177c05d9e..6e8acda92a395 100644 --- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs +++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs @@ -36,12 +36,6 @@ public async Task GetOrCreateProviderAsy { // the following code requires UI thread: await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - return GetOrCreateProviderOnMainThread(); - } - - private VisualStudioDiagnosticAnalyzerProvider GetOrCreateProviderOnMainThread() - { - Contract.ThrowIfFalse(_threadingContext.JoinableTaskContext.IsOnMainThread); if (_lazyProvider != null) { diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb index a47583d7e3932..518a751398c70 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb @@ -133,26 +133,20 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim "Project", LanguageNames.CSharp, CancellationToken.None) ' adding just Razor dependency and not the main source generator is a no-op - Using project.CreateBatchScope() - project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) - End Using + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences) ' removing just Razor dependency and not the main source generator is a no-op - Using project.CreateBatchScope() - project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) - End Using + project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences) ' add Razor source generator and a couple more other analyzer filess: - Using project.CreateBatchScope() - project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) - project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) - project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Some other directory", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) - project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Dir", "File.dll")) - End Using + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Some other directory", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Dir", "File.dll")) AssertEx.Equal( { @@ -164,11 +158,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) ' add Razor source generator again: - Using project.CreateBatchScope() - Assert.Throws(Of ArgumentException)( - "fullPath", - Sub() project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"))) - End Using + Assert.Throws(Of ArgumentException)( + "fullPath", + Sub() project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"))) AssertEx.Equal( { @@ -180,11 +172,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) ' remove: - Using project.CreateBatchScope() - project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) - project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) - project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Some other directory", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) - End Using + project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll")) + project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) + project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Some other directory", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll")) AssertEx.Equal( { @@ -192,11 +182,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) ' remove again: - Using project.CreateBatchScope() - Assert.Throws(Of ArgumentException)( - "fullPath", - Sub() project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"))) - End Using + Assert.Throws(Of ArgumentException)( + "fullPath", + Sub() project.RemoveAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll"))) AssertEx.Equal( {