-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Do not load CodeStyle analyzers added by the SDK #75250
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,57 @@ 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 | ||
|
||
<WpfFact> | ||
Public Async Function CodeStyleAnalyzers_CSharp_FromSdk_AreIgnored() As Task | ||
Using environment = New TestEnvironment() | ||
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")) | ||
Comment on lines
+215
to
+218
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do resource binaries get added too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't believe resource binaries are added as AnalyzerReferences to the project. I would expect they are loaded by the AnalyzerAssemblyLoader. |
||
|
||
' 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 | ||
|
||
<WpfFact> | ||
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") | ||
}, environment.Workspace.CurrentSolution.Projects.Single().AnalyzerReferences.Select(Function(r) r.FullPath)) | ||
End Using | ||
End Function | ||
End Class | ||
End Namespace |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1030,8 +1030,39 @@ public void RemoveAnalyzerReference(string fullPath) | |
} | ||
} | ||
|
||
private OneOrMany<string> 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<string>.Empty; | ||
JoeRobich marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to call it out explicitly, doing this here will:
The latter we can of course defer for this change (since this has huge dogfooding benefit), but wanted to call out the former. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really don't love that we're expanding this mechanism, given the work we're doing to allow the SDK to redirect analyzers via #74820 That isn't a reason to block on this, given its not done yet, but could we open an issue to investigate if we could replace this with the new API when it's introduced? |
||
} | ||
|
||
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 = 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) => Language switch | ||
{ | ||
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<string> s_razorSourceGeneratorAssemblyNames = | ||
[ | ||
"Microsoft.NET.Sdk.Razor.SourceGenerators", | ||
|
@@ -1041,33 +1072,33 @@ public void RemoveAnalyzerReference(string fullPath) | |
private static readonly ImmutableArray<string> s_razorSourceGeneratorAssemblyRootedFileNames = s_razorSourceGeneratorAssemblyNames.SelectAsArray( | ||
assemblyName => PathUtilities.DirectorySeparatorStr + assemblyName + ".dll"); | ||
|
||
private OneOrMany<string> GetMappedAnalyzerPaths(string fullPath) | ||
private static bool IsSdkRazorSourceGenerator(string fullPath) => DirectoryNameEndsWith(fullPath, s_razorSourceGeneratorSdkDirectory); | ||
|
||
private OneOrMany<string> GetMappedRazorSourceGenerator(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); | ||
var vsixRazorAnalyzers = _hostInfo.HostDiagnosticAnalyzerProvider.GetAnalyzerReferencesInExtensions().SelectAsArray( | ||
predicate: item => item.extensionId == RazorVsixExtensionId, | ||
selector: item => item.reference.FullPath); | ||
|
||
if (!vsixRazorAnalyzers.IsEmpty) | ||
if (!vsixRazorAnalyzers.IsEmpty) | ||
{ | ||
if (s_razorSourceGeneratorAssemblyRootedFileNames.Any( | ||
static (fileName, fullPath) => fullPath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase), fullPath)) | ||
{ | ||
if (s_razorSourceGeneratorAssemblyRootedFileNames.Any( | ||
static (fileName, fullPath) => fullPath.EndsWith(fileName, StringComparison.OrdinalIgnoreCase), fullPath)) | ||
{ | ||
return OneOrMany.Create(vsixRazorAnalyzers); | ||
} | ||
|
||
return OneOrMany.Create(ImmutableArray<string>.Empty); | ||
return OneOrMany.Create(vsixRazorAnalyzers); | ||
} | ||
|
||
return OneOrMany<string>.Empty; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My one fear is that this is hard to test in a unit test. Do we have an item on the queue to do manual verification of Razor / Code style once this is all merged in? Have we thought about adding an integration test that verifies we redirected these loads? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should be easy enough to write an integration test for this. Will do as a follow up after the snap. |
||
} | ||
|
||
return OneOrMany.Create(fullPath); | ||
} | ||
|
||
private static string CreateDirectoryPathFragment(params string[] paths) => Path.Combine([" ", .. paths, " "]).Trim(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So Path.Combine will add the separator then we trim it off the spaces. Was just trying to put the work onto Path.Combine. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it is dumb. Happy to revert it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely not dumb, just a little too tricky for me to spot without the explanation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am going to do a follow up that adds an integration test. Will add a comment here as well. |
||
|
||
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) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are all of these DLLs already in box that we're mapping to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably needs VB too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the in box code style analyzers. We are ensuring that they do not get returned as AnalyzerReferences from the Project. Which means they will not be used for analysis.