Skip to content

Commit

Permalink
Merge pull request dotnet#75250 from dotnet/dev/jorobich/discard-code…
Browse files Browse the repository at this point in the history
…style-analyzers
  • Loading branch information
JoeRobich authored Sep 27, 2024
2 parents 93a5ed9 + 71bec2e commit 23f900a
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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"))

' 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
Expand Up @@ -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;
}

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",
Expand All @@ -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;
}

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)
Expand Down

0 comments on commit 23f900a

Please sign in to comment.