diff --git a/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs b/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs
new file mode 100644
index 0000000000000..3e58c6a824b5f
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Diagnostics/IVisualStudioDiagnosticAnalyzerProviderFactory.cs
@@ -0,0 +1,17 @@
+// 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;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics
+{
+ ///
+ /// Abstraction for testing purposes.
+ ///
+ internal interface IVisualStudioDiagnosticAnalyzerProviderFactory
+ {
+ Task GetOrCreateProviderAsync(CancellationToken cancellationToken);
+ }
+}
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..6e8acda92a395
--- /dev/null
+++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.Factory.cs
@@ -0,0 +1,60 @@
+// 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;
+using System.Threading;
+
+namespace Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics
+{
+ internal partial class VisualStudioDiagnosticAnalyzerProvider
+ {
+ [Export(typeof(IVisualStudioDiagnosticAnalyzerProviderFactory)), Shared]
+ internal sealed class Factory : IVisualStudioDiagnosticAnalyzerProviderFactory
+ {
+ 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(CancellationToken cancellationToken)
+ {
+ // the following code requires UI thread:
+ await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+
+ 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..f7ba92198a7ca 100644
--- a/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs
+++ b/src/VisualStudio/Core/Def/Diagnostics/VisualStudioDiagnosticAnalyzerProvider.WorkspaceEventListener.cs
@@ -5,8 +5,10 @@
using System;
using System.Composition;
using System.Reflection;
+using System.Threading;
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 +29,16 @@ internal partial class VisualStudioDiagnosticAnalyzerProvider
internal sealed class WorkspaceEventListener : IEventListener
{
private readonly IAsynchronousOperationListener _listener;
- private readonly IThreadingContext _threadingContext;
- private readonly IServiceProvider _serviceProvider;
+ private readonly IVisualStudioDiagnosticAnalyzerProviderFactory _providerFactory;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public WorkspaceEventListener(
- IThreadingContext threadingContext,
- Shell.SVsServiceProvider serviceProvider,
- IAsynchronousOperationListenerProvider listenerProvider)
+ IAsynchronousOperationListenerProvider listenerProvider,
+ IVisualStudioDiagnosticAnalyzerProviderFactory providerFactory)
{
- _threadingContext = threadingContext;
- _serviceProvider = serviceProvider;
_listener = listenerProvider.GetListener(nameof(Workspace));
+ _providerFactory = providerFactory;
}
public void StartListening(Workspace workspace, object serviceOpt)
@@ -57,11 +56,11 @@ private async Task InitializeWorkspaceAsync(ISolutionAnalyzerSetterWorkspaceServ
{
try
{
- var provider = await CreateProviderAsync().ConfigureAwait(false);
+ var provider = await _providerFactory.GetOrCreateProviderAsync(CancellationToken.None).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 +68,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..3d285f6330263 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_Identifier = extension_HeaderType.GetRuntimeProperty("Identifier");
+ var identifier = (string)extension_HeaderType_Identifier.GetValue(extension_Header);
- // 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(new AnalyzerFileReference(assemblyPath, AnalyzerAssemblyLoader), identifier);
}
}
@@ -89,7 +92,8 @@ 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));
+ // 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)
{
@@ -99,7 +103,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/VisualStudioProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs
index 310bd18639769..b2c5e26376030 100644
--- a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs
+++ b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProject.cs
@@ -13,12 +13,14 @@
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;
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 +28,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 +149,7 @@ internal VisualStudioProject(
VisualStudioWorkspaceImpl workspace,
ImmutableArray> dynamicFileInfoProviders,
HostDiagnosticUpdateSource hostDiagnosticUpdateSource,
+ VisualStudioDiagnosticAnalyzerProvider vsixAnalyzerProvider,
ProjectId id,
string displayName,
string language,
@@ -156,6 +161,7 @@ internal VisualStudioProject(
_workspace = workspace;
_dynamicFileInfoProviders = dynamicFileInfoProviders;
_hostDiagnosticUpdateSource = hostDiagnosticUpdateSource;
+ _vsixAnalyzerProvider = vsixAnalyzerProvider;
Id = id;
Language = language;
@@ -877,39 +883,48 @@ public void AddAnalyzerReference(string fullPath)
{
CompilerPathUtilities.RequireAbsolutePath(fullPath, nameof(fullPath));
+ var mappedPaths = GetMappedAnalyzerPaths(fullPath);
+
using (_gate.DisposableWait())
{
- if (_analyzerPathsToAnalyzers.ContainsKey(fullPath))
+ // check all mapped paths first, so that all analyzers are either added or not
+ foreach (var mappedFullPath in mappedPaths)
{
- throw new ArgumentException($"'{fullPath}' has already been added to this project.", nameof(fullPath));
+ if (_analyzerPathsToAnalyzers.ContainsKey(mappedFullPath))
+ {
+ 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 mappedPaths)
{
- // Nope, we actually need to make a new one.
- var visualStudioAnalyzer = new VisualStudioAnalyzer(
- fullPath,
- _hostDiagnosticUpdateSource,
- Id,
- Language);
-
- _analyzerPathsToAnalyzers.Add(fullPath, visualStudioAnalyzer);
-
- if (_activeBatchScopes > 0)
+ // 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()));
+ }
}
}
}
@@ -922,35 +937,71 @@ public void RemoveAnalyzerReference(string fullPath)
throw new ArgumentException("message", nameof(fullPath));
}
+ var mappedPaths = GetMappedAnalyzerPaths(fullPath);
+
using (_gate.DisposableWait())
{
- if (!_analyzerPathsToAnalyzers.TryGetValue(fullPath, out var visualStudioAnalyzer))
+ // check all mapped paths first, so that all analyzers are either removed or not
+ foreach (var mappedFullPath in mappedPaths)
{
- throw new ArgumentException($"'{fullPath}' is not an analyzer of this project.", nameof(fullPath));
+ if (!_analyzerPathsToAnalyzers.ContainsKey(mappedFullPath))
+ {
+ throw new ArgumentException($"'{fullPath}' is not an analyzer of this project.", nameof(fullPath));
+ }
}
- _analyzerPathsToAnalyzers.Remove(fullPath);
-
- if (_activeBatchScopes > 0)
+ foreach (var mappedFullPath in mappedPaths)
{
- // 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))
+ var visualStudioAnalyzer = _analyzerPathsToAnalyzers[mappedFullPath];
+
+ _analyzerPathsToAnalyzers.Remove(mappedFullPath);
+
+ 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 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)
+ {
+ // 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))
+ {
+ if (fullPath.EndsWith(s_razorSourceGeneratorMainAssemblyRootedFileName, StringComparison.OrdinalIgnoreCase))
{
- _workspace.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceRemoved(Id, visualStudioAnalyzer.GetReference()));
- visualStudioAnalyzer.Dispose();
+ return OneOrMany.Create(_vsixAnalyzerProvider.GetAnalyzerReferencesInExtensions().SelectAsArray(
+ predicate: item => item.extensionId == RazorVsixExtensionId,
+ selector: item => item.reference.FullPath));
}
+
+ return OneOrMany.Create(ImmutableArray.Empty);
}
+
+ return OneOrMany.Create(fullPath);
}
#endregion
diff --git a/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs b/src/VisualStudio/Core/Def/ProjectSystem/VisualStudioProjectFactory.cs
index 42871a3177534..3b862e404dfac 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 IVisualStudioDiagnosticAnalyzerProviderFactory _vsixAnalyzerProviderFactory;
private readonly Shell.IAsyncServiceProvider _serviceProvider;
[ImportingConstructor]
@@ -42,12 +44,14 @@ public VisualStudioProjectFactory(
VisualStudioWorkspaceImpl visualStudioWorkspaceImpl,
[ImportMany] IEnumerable> fileInfoProviders,
HostDiagnosticUpdateSource hostDiagnosticUpdateSource,
+ IVisualStudioDiagnosticAnalyzerProviderFactory 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 = await _vsixAnalyzerProviderFactory.GetOrCreateProviderAsync(cancellationToken).ConfigureAwait(false);
+
// 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/VisualStudioDiagnosticAnalyzerProviderTests.vb b/src/VisualStudio/Core/Test/Diagnostics/VisualStudioDiagnosticAnalyzerProviderTests.vb
index bf2bb51780089..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("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"}, "Vsix")}),
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"}, "Vsix")}),
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/VisualStudioProjectTests/AnalyzerReferenceTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb
index f4c412bebb0f3..518a751398c70 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,88 @@ 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
+ 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
+ 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:
+ 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(
+ {
+ 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:
+ 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(
+ {
+ 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:
+ 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(
+ {
+ Path.Combine(TempRoot.Root, "Dir", "File.dll")
+ }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath))
+
+ ' remove again:
+ 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(
+ {
+ 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/Core/Test/Diagnostics/MockExtensionManager.vb b/src/VisualStudio/TestUtilities2/MockExtensionManager.vb
similarity index 62%
rename from src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb
rename to src/VisualStudio/TestUtilities2/MockExtensionManager.vb
index f1cae1e1428e0..debf644c7f18b 100644
--- a/src/VisualStudio/Core/Test/Diagnostics/MockExtensionManager.vb
+++ b/src/VisualStudio/TestUtilities2/MockExtensionManager.vb
@@ -2,44 +2,38 @@
' 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
+ Private ReadOnly _extensions As (Paths As String(), Id As String)()
- Public Sub New(contentType As String, ParamArray locations() As String)
+ Public Sub New(Optional extensions As (Paths As String(), Id As String)() = Nothing,
+ Optional contentType As String = "Microsoft.VisualStudio.Analyzer")
_contentType = contentType
- _locations = locations
+ _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"))
@@ -50,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
@@ -66,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
@@ -86,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
new file mode 100644
index 0000000000000..2bca62d09f7d7
--- /dev/null
+++ b/src/VisualStudio/TestUtilities2/MockVisualStudioDiagnosticAnalyzerProviderFactory.vb
@@ -0,0 +1,27 @@
+' 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 System.Threading
+Imports Microsoft.CodeAnalysis.Host.Mef
+Imports Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics
+
+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(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
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),