From 53c4588bc1a3c7fe9870bdf85c14e5412d9772a6 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Thu, 26 Sep 2024 00:47:50 -0700 Subject: [PATCH 1/2] Do not load CodeStyle analyzers added by the SDK --- .../AnalyzerReferenceTests.vb | 33 +++++++- .../ProjectSystem/ProjectSystemProject.cs | 78 ++++++++++++++----- 2 files changed, 92 insertions(+), 19 deletions(-) diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb index 47a8b12842377..1afac4b42b653 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb @@ -195,7 +195,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( "Project", LanguageNames.CSharp, CancellationToken.None) - ' add Razor source generator and a couple more other analyzer filess: + ' add Razor source generator and a couple more other analyzer files: Dim path1 = Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "Microsoft.NET.Sdk.Razor.SourceGenerators.dll") Dim path2 = Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk.Razor", "source-generators", "SdkDependency1.dll") project.AddAnalyzerReference(path1) @@ -204,5 +204,36 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim AssertEx.Equal({path1, path2}, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) End Using End Function + + + Public Async Function CodeStyleAnalyzers_FromSdk() As Task + Using environment = New TestEnvironment() + Dim providerFactory = DirectCast(environment.ExportProvider.GetExportedValue(Of IVisualStudioDiagnosticAnalyzerProviderFactory), MockVisualStudioDiagnosticAnalyzerProviderFactory) + providerFactory.Extensions = + { + ({ + Path.Combine(TempRoot.Root, "File.dll") + }, + "AnotherExtension") + } + + Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( + "Project", LanguageNames.CSharp, CancellationToken.None) + + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "Microsoft.CodeAnalysis.CodeStyle.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "Microsoft.CodeAnalysis.CodeStyle.Fixes.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "Microsoft.CodeAnalysis.CSharp.CodeStyle.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes.dll")) + + Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences) + + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Dir", "File.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 End Class End Namespace diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index b963a26744eaa..a016418594dfa 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -1030,6 +1030,49 @@ public void RemoveAnalyzerReference(string fullPath) } } + private OneOrMany GetMappedAnalyzerPaths(string fullPath) + { + fullPath = Path.GetFullPath(fullPath); + + if (IsSdkCodeStyleAnalyzer(fullPath)) + { + // We discard the CodeStyle analyzers added by the SDK when the EnforceCodeStyleInBuild property is set. + // The same analyzers ship in-box as part of the Features layer and are version matched to the compiler. + return OneOrMany.Empty; + } + + if (IsSdkRazorSourceGenerator(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 + return GetMappedRazorSourceGenerator(fullPath); + } + + return OneOrMany.Create(fullPath); + } + + private static readonly string s_csharpCodeStyleAnalyzerSdkDirectory = Path.Combine("Sdks", "Microsoft.NET.Sdk", "codestyle", "cs") + PathUtilities.DirectorySeparatorStr; + private static readonly string s_visualBasicCodeStyleAnalyzerSdkDirectory = Path.Combine("Sdks", "Microsoft.NET.Sdk", "codestyle", "vb") + PathUtilities.DirectorySeparatorStr; + + private bool IsSdkCodeStyleAnalyzer(string fullPath) + { + if (Language == LanguageNames.CSharp && + fullPath.LastIndexOf(s_csharpCodeStyleAnalyzerSdkDirectory, StringComparison.OrdinalIgnoreCase) + s_csharpCodeStyleAnalyzerSdkDirectory.Length - 1 == + fullPath.LastIndexOf(Path.DirectorySeparatorChar)) + { + return true; + } + + if (Language == LanguageNames.VisualBasic && + fullPath.LastIndexOf(s_visualBasicCodeStyleAnalyzerSdkDirectory, StringComparison.OrdinalIgnoreCase) + s_visualBasicCodeStyleAnalyzerSdkDirectory.Length - 1 == + fullPath.LastIndexOf(Path.DirectorySeparatorChar)) + { + return true; + } + + return false; + } + internal 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 ImmutableArray s_razorSourceGeneratorAssemblyNames = @@ -1041,28 +1084,27 @@ public void RemoveAnalyzerReference(string fullPath) private static readonly ImmutableArray s_razorSourceGeneratorAssemblyRootedFileNames = s_razorSourceGeneratorAssemblyNames.SelectAsArray( assemblyName => PathUtilities.DirectorySeparatorStr + assemblyName + ".dll"); - private OneOrMany GetMappedAnalyzerPaths(string fullPath) + private static bool IsSdkRazorSourceGenerator(string fullPath) { - fullPath = Path.GetFullPath(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)) - { - var vsixRazorAnalyzers = _hostInfo.HostDiagnosticAnalyzerProvider.GetAnalyzerReferencesInExtensions().SelectAsArray( - predicate: item => item.extensionId == RazorVsixExtensionId, - selector: item => item.reference.FullPath); + return fullPath.LastIndexOf(s_razorSourceGeneratorSdkDirectory, StringComparison.OrdinalIgnoreCase) + s_razorSourceGeneratorSdkDirectory.Length - 1 == + fullPath.LastIndexOf(Path.DirectorySeparatorChar); + } - if (!vsixRazorAnalyzers.IsEmpty) - { - if (s_razorSourceGeneratorAssemblyRootedFileNames.Any( - static (fileName, fullPath) => fullPath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase), fullPath)) - { - return OneOrMany.Create(vsixRazorAnalyzers); - } + private OneOrMany GetMappedRazorSourceGenerator(string fullPath) + { + var vsixRazorAnalyzers = _hostInfo.HostDiagnosticAnalyzerProvider.GetAnalyzerReferencesInExtensions().SelectAsArray( + predicate: item => item.extensionId == RazorVsixExtensionId, + selector: item => item.reference.FullPath); - return OneOrMany.Create(ImmutableArray.Empty); + if (!vsixRazorAnalyzers.IsEmpty) + { + if (s_razorSourceGeneratorAssemblyRootedFileNames.Any( + static (fileName, fullPath) => fullPath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase), fullPath)) + { + return OneOrMany.Create(vsixRazorAnalyzers); } + + return OneOrMany.Empty; } return OneOrMany.Create(fullPath); From 71bec2ef913f199b066a12a73454a94f4aa7d857 Mon Sep 17 00:00:00 2001 From: Joey Robichaud Date: Thu, 26 Sep 2024 15:27:32 -0700 Subject: [PATCH 2/2] Review feedback --- .../AnalyzerReferenceTests.vb | 41 ++++++++++++++----- .../ProjectSystem/ProjectSystemProject.cs | 39 +++++++----------- 2 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb index 1afac4b42b653..a8354283cebcf 100644 --- a/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb +++ b/src/VisualStudio/Core/Test/ProjectSystemShim/VisualStudioProjectTests/AnalyzerReferenceTests.vb @@ -206,29 +206,50 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim End Function - Public Async Function CodeStyleAnalyzers_FromSdk() As Task + Public Async Function CodeStyleAnalyzers_CSharp_FromSdk_AreIgnored() As Task Using environment = New TestEnvironment() - Dim providerFactory = DirectCast(environment.ExportProvider.GetExportedValue(Of IVisualStudioDiagnosticAnalyzerProviderFactory), MockVisualStudioDiagnosticAnalyzerProviderFactory) - providerFactory.Extensions = - { - ({ - Path.Combine(TempRoot.Root, "File.dll") - }, - "AnotherExtension") - } - Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( "Project", LanguageNames.CSharp, CancellationToken.None) + ' These are the in-box C# codestyle analyzers that ship with the SDK project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "Microsoft.CodeAnalysis.CodeStyle.dll")) project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "Microsoft.CodeAnalysis.CodeStyle.Fixes.dll")) project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "Microsoft.CodeAnalysis.CSharp.CodeStyle.dll")) project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "cs", "Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes.dll")) + ' Ensure they are not returned when getting AnalyzerReferences + Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences) + + ' Add a non-codestyle analyzer to the project + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Dir", "File.dll")) + + ' Ensure it is returned as expected + AssertEx.Equal( + { + Path.Combine(TempRoot.Root, "Dir", "File.dll") + }, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) + End Using + End Function + + + Public Async Function CodeStyleAnalyzers_VisualBasic_FromSdk_AreIgnored() As Task + Using environment = New TestEnvironment() + Dim project = Await environment.ProjectFactory.CreateAndAddToWorkspaceAsync( + "Project", LanguageNames.VisualBasic, CancellationToken.None) + + ' These are the in-box VB codestyle analyzers that ship with the SDK + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "vb", "Microsoft.CodeAnalysis.CodeStyle.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "vb", "Microsoft.CodeAnalysis.CodeStyle.Fixes.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "vb", "Microsoft.CodeAnalysis.VisualBasic.CodeStyle.dll")) + project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Sdks", "Microsoft.NET.Sdk", "codestyle", "vb", "Microsoft.CodeAnalysis.VisualBasic.CodeStyle.Fixes.dll")) + + ' Ensure they are not returned when getting AnalyzerReferences Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences) + ' Add a non-codestyle analyzer to the project project.AddAnalyzerReference(Path.Combine(TempRoot.Root, "Dir", "File.dll")) + ' Ensure it is returned as expected AssertEx.Equal( { Path.Combine(TempRoot.Root, "Dir", "File.dll") diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs index a016418594dfa..cfcfaa42c8103 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs @@ -1051,30 +1051,18 @@ private OneOrMany GetMappedAnalyzerPaths(string fullPath) return OneOrMany.Create(fullPath); } - private static readonly string s_csharpCodeStyleAnalyzerSdkDirectory = Path.Combine("Sdks", "Microsoft.NET.Sdk", "codestyle", "cs") + PathUtilities.DirectorySeparatorStr; - private static readonly string s_visualBasicCodeStyleAnalyzerSdkDirectory = Path.Combine("Sdks", "Microsoft.NET.Sdk", "codestyle", "vb") + PathUtilities.DirectorySeparatorStr; + private static readonly string s_csharpCodeStyleAnalyzerSdkDirectory = CreateDirectoryPathFragment("Sdks", "Microsoft.NET.Sdk", "codestyle", "cs"); + private static readonly string s_visualBasicCodeStyleAnalyzerSdkDirectory = CreateDirectoryPathFragment("Sdks", "Microsoft.NET.Sdk", "codestyle", "vb"); - private bool IsSdkCodeStyleAnalyzer(string fullPath) + private bool IsSdkCodeStyleAnalyzer(string fullPath) => Language switch { - if (Language == LanguageNames.CSharp && - fullPath.LastIndexOf(s_csharpCodeStyleAnalyzerSdkDirectory, StringComparison.OrdinalIgnoreCase) + s_csharpCodeStyleAnalyzerSdkDirectory.Length - 1 == - fullPath.LastIndexOf(Path.DirectorySeparatorChar)) - { - return true; - } - - if (Language == LanguageNames.VisualBasic && - fullPath.LastIndexOf(s_visualBasicCodeStyleAnalyzerSdkDirectory, StringComparison.OrdinalIgnoreCase) + s_visualBasicCodeStyleAnalyzerSdkDirectory.Length - 1 == - fullPath.LastIndexOf(Path.DirectorySeparatorChar)) - { - return true; - } - - return false; - } + LanguageNames.CSharp => DirectoryNameEndsWith(fullPath, s_csharpCodeStyleAnalyzerSdkDirectory), + LanguageNames.VisualBasic => DirectoryNameEndsWith(fullPath, s_visualBasicCodeStyleAnalyzerSdkDirectory), + _ => false, + }; internal 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_razorSourceGeneratorSdkDirectory = CreateDirectoryPathFragment("Sdks", "Microsoft.NET.Sdk.Razor", "source-generators"); private static readonly ImmutableArray s_razorSourceGeneratorAssemblyNames = [ "Microsoft.NET.Sdk.Razor.SourceGenerators", @@ -1084,11 +1072,7 @@ private bool IsSdkCodeStyleAnalyzer(string fullPath) private static readonly ImmutableArray s_razorSourceGeneratorAssemblyRootedFileNames = s_razorSourceGeneratorAssemblyNames.SelectAsArray( assemblyName => PathUtilities.DirectorySeparatorStr + assemblyName + ".dll"); - private static bool IsSdkRazorSourceGenerator(string fullPath) - { - return fullPath.LastIndexOf(s_razorSourceGeneratorSdkDirectory, StringComparison.OrdinalIgnoreCase) + s_razorSourceGeneratorSdkDirectory.Length - 1 == - fullPath.LastIndexOf(Path.DirectorySeparatorChar); - } + private static bool IsSdkRazorSourceGenerator(string fullPath) => DirectoryNameEndsWith(fullPath, s_razorSourceGeneratorSdkDirectory); private OneOrMany GetMappedRazorSourceGenerator(string fullPath) { @@ -1110,6 +1094,11 @@ private OneOrMany GetMappedRazorSourceGenerator(string fullPath) return OneOrMany.Create(fullPath); } + private static string CreateDirectoryPathFragment(params string[] paths) => Path.Combine([" ", .. paths, " "]).Trim(); + + private static bool DirectoryNameEndsWith(string fullPath, string ending) => fullPath.LastIndexOf(ending, StringComparison.OrdinalIgnoreCase) + ending.Length - 1 == + fullPath.LastIndexOf(Path.DirectorySeparatorChar); + #endregion private void DocumentFileChangeContext_FileChanged(object? sender, string fullFilePath)