-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add analyzer redirecting VSIX #42861
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
Changes from 8 commits
62154cf
cdb4892
2ed93ed
53a5074
5928f32
5165f0f
9807811
4e01a87
cdac6cc
7a31ffb
586ced5
c4a206a
e65c1c0
61af4e8
245b2f1
f5d671e
92e2284
f0b86a4
d5a3924
5da7cda
6a8f110
8680087
f73918a
74518c1
d8195ab
cf5c1f3
8b61147
7116b32
4c32363
bde0f8d
1971566
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 |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| using Microsoft.VisualStudio.Shell; | ||
|
|
||
| namespace Microsoft.Net.Sdk.AnalyzerRedirecting; | ||
|
|
||
| [Guid("ef89a321-14da-4de4-8f71-9bf1feea15aa")] | ||
| public sealed class AnalyzerRedirectingPackage : AsyncPackage; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net472</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
|
|
||
| <IsShipping>false</IsShipping> | ||
| <ExcludeFromSourceBuild>true</ExcludeFromSourceBuild> | ||
|
|
||
| <!-- VSIX --> | ||
| <VSSDKTargetPlatformRegRootSuffix>RoslynDev</VSSDKTargetPlatformRegRootSuffix> | ||
marcpopMSFT marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <GeneratePkgDefFile>true</GeneratePkgDefFile> | ||
| <IncludeAssemblyInVSIXContainer>true</IncludeAssemblyInVSIXContainer> | ||
| <IncludeDebugSymbolsInVSIXContainer>true</IncludeDebugSymbolsInVSIXContainer> | ||
| <IncludeDebugSymbolsInLocalVSIXDeployment>true</IncludeDebugSymbolsInLocalVSIXDeployment> | ||
| <IncludeCopyLocalReferencesInVSIXContainer>false</IncludeCopyLocalReferencesInVSIXContainer> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.CodeAnalysis.CSharp" /> | ||
| <PackageReference Include="Microsoft.VisualStudio.Sdk" /> | ||
| </ItemGroup> | ||
|
|
||
| <ItemGroup> | ||
| <InternalsVisibleTo Include="Microsoft.Net.Sdk.AnalyzerRedirecting.Tests" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Immutable; | ||
| using System.ComponentModel.Composition; | ||
| using Microsoft.CodeAnalysis; | ||
|
|
||
| // Example: | ||
| // FullPath: "C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\System.Windows.Forms.Analyzers.dll" | ||
| // ProductVersion: "8.0.8" | ||
| // PathSuffix: "analyzers\dotnet" | ||
| using AnalyzerInfo = (string FullPath, string ProductVersion, string PathSuffix); | ||
|
|
||
| namespace Microsoft.Net.Sdk.AnalyzerRedirecting; | ||
|
|
||
| [Export(typeof(IAnalyzerAssemblyRedirector))] | ||
| public sealed class SdkAnalyzerAssemblyRedirector : IAnalyzerAssemblyRedirector | ||
| { | ||
| private readonly string? _insertedAnalyzersDirectory; | ||
| private readonly Lazy<ImmutableDictionary<string, List<AnalyzerInfo>>> _analyzerMap; | ||
jjonescz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| [ImportingConstructor] | ||
| public SdkAnalyzerAssemblyRedirector() | ||
| : this(Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "SDK", "RuntimeAnalyzers"))) { } | ||
|
|
||
| // Internal for testing. | ||
| internal SdkAnalyzerAssemblyRedirector(string? insertedAnalyzersDirectory) | ||
| { | ||
| _insertedAnalyzersDirectory = insertedAnalyzersDirectory; | ||
| _analyzerMap = new(CreateAnalyzerMap); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Map from analyzer assembly name (file name without extension) to a list of matching analyzers. | ||
| /// </summary> | ||
| private ImmutableDictionary<string, List<AnalyzerInfo>> AnalyzerMap => _analyzerMap.Value; | ||
|
|
||
| private ImmutableDictionary<string, List<AnalyzerInfo>> CreateAnalyzerMap() | ||
| { | ||
| var builder = ImmutableDictionary.CreateBuilder<string, List<AnalyzerInfo>>(StringComparer.OrdinalIgnoreCase); | ||
|
|
||
| // Expects layout like: | ||
| // VsInstallDir\SDK\RuntimeAnalyzers\WindowsDesktopAnalyzers\8.0.8\analyzers\dotnet\System.Windows.Forms.Analyzers.dll | ||
| // ~~~~~~~~~~~~~~~~~~~~~~~ = topLevelDirectory | ||
| // ~~~~~ = versionDirectory | ||
| // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ = analyzerPath | ||
|
|
||
| foreach (string topLevelDirectory in Directory.EnumerateDirectories(_insertedAnalyzersDirectory)) | ||
| { | ||
| foreach (string versionDirectory in Directory.EnumerateDirectories(topLevelDirectory)) | ||
| { | ||
| foreach (string analyzerPath in Directory.EnumerateFiles(versionDirectory, "*.dll", SearchOption.AllDirectories)) | ||
| { | ||
| if (!analyzerPath.StartsWith(versionDirectory, StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| continue; | ||
| } | ||
|
|
||
| string version = Path.GetFileName(versionDirectory); | ||
| string analyzerName = Path.GetFileNameWithoutExtension(analyzerPath); | ||
| string pathSuffix = analyzerPath.Substring(versionDirectory.Length + (EndsWithSlash(versionDirectory) ? 0 : 1)); | ||
ericstj marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| pathSuffix = Path.GetDirectoryName(pathSuffix); | ||
|
|
||
| AnalyzerInfo analyzer = new() { FullPath = analyzerPath, ProductVersion = version, PathSuffix = pathSuffix }; | ||
|
|
||
| if (builder.TryGetValue(analyzerName, out var existing)) | ||
| { | ||
| existing.Add(analyzer); | ||
| } | ||
| else | ||
| { | ||
| builder.Add(analyzerName, [analyzer]); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return builder.ToImmutable(); | ||
| } | ||
|
|
||
| public string? RedirectPath(string fullPath) | ||
| { | ||
| if (AnalyzerMap.TryGetValue(Path.GetFileNameWithoutExtension(fullPath), out var analyzers)) | ||
| { | ||
| foreach (AnalyzerInfo analyzer in analyzers) | ||
| { | ||
| var directoryPath = Path.GetDirectoryName(fullPath); | ||
| if (endsWithIgnoringTrailingSlashes(directoryPath, analyzer.PathSuffix) && | ||
| majorAndMinorVersionsMatch(directoryPath, analyzer.PathSuffix, analyzer.ProductVersion)) | ||
| { | ||
| return analyzer.FullPath; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
|
|
||
| static bool majorAndMinorVersionsMatch(string directoryPath, string pathSuffix, string version) | ||
| { | ||
| // Find the version number in the directory path - it is in the directory name before the path suffix. | ||
jjonescz marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // Example: | ||
| // "C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\" = directoryPath | ||
| // ~~~~~~~~~~~~~~~~ = pathSuffix | ||
| // ~~~~~ = directoryPathVersion | ||
| int index = directoryPath.LastIndexOf(pathSuffix, StringComparison.OrdinalIgnoreCase); | ||
| if (index < 0) | ||
| { | ||
| return false; | ||
| } | ||
| string directoryPathVersion = Path.GetFileName(Path.GetDirectoryName(directoryPath.Substring(0, index))); | ||
|
|
||
| return getMajorMinorPart(directoryPathVersion) == getMajorMinorPart(version); | ||
|
||
| } | ||
|
|
||
| static string getMajorMinorPart(string version) | ||
| { | ||
| int firstDotIndex = version.IndexOf('.'); | ||
| if (firstDotIndex < 0) | ||
| { | ||
| return version; | ||
| } | ||
|
|
||
| int secondDotIndex = version.IndexOf('.', firstDotIndex + 1); | ||
| if (secondDotIndex < 0) | ||
| { | ||
| return version; | ||
| } | ||
|
|
||
| return version.Substring(0, secondDotIndex); | ||
| } | ||
|
|
||
| static bool endsWithIgnoringTrailingSlashes(string s, string suffix) | ||
| { | ||
| var sEndsWithSlash = EndsWithSlash(s); | ||
| var suffixEndsWithSlash = EndsWithSlash(suffix); | ||
| var index = s.LastIndexOf(suffix, StringComparison.OrdinalIgnoreCase); | ||
ericstj marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return index >= 0 && index + suffix.Length - (suffixEndsWithSlash ? 1 : 0) == s.Length - (sEndsWithSlash ? 1 : 0); | ||
| } | ||
| } | ||
|
|
||
| private static bool EndsWithSlash(string s) => !string.IsNullOrEmpty(s) && s[s.Length - 1] is '/' or '\\'; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011"> | ||
| <Metadata> | ||
| <Identity Id="a39fdd10-6d59-4dca-b205-053f23b2afea" Version="|%CurrentProject%;GetVsixVersion|" Language="en-US" Publisher="Microsoft" /> | ||
| <DisplayName>.NET SDK Analyzer Redirecting</DisplayName> | ||
| <Description xml:space="preserve">.NET SDK Analyzer Redirecting Package.</Description> | ||
| </Metadata> | ||
| <Dependencies> | ||
| <Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.7.2,)" /> | ||
| </Dependencies> | ||
| <Assets> | ||
| <Asset Type="Microsoft.VisualStudio.MefComponent" Path="|%CurrentProject%|" d:Source="Project" d:ProjectName="%CurrentProject%" /> | ||
| <Asset Type="Microsoft.VisualStudio.VsPackage" Path="|%CurrentProject%;PkgdefProjectOutputGroup|" d:Source="Project" d:ProjectName="%CurrentProject%" /> | ||
| </Assets> | ||
| <Prerequisites> | ||
| <Prerequisite Id="Microsoft.VisualStudio.Component.CoreEditor" Version="[17.0,18.0)" DisplayName="Visual Studio core editor" /> | ||
| </Prerequisites> | ||
| <Installation Experimental="true"> | ||
| <InstallationTarget Id="Microsoft.VisualStudio.Pro" Version="[17.0,18.0)"> | ||
| <ProductArchitecture>amd64</ProductArchitecture> | ||
| </InstallationTarget> | ||
| </Installation> | ||
| </PackageManifest> |
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.
Do you know how often these get updated and how best to keep them updated?
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.
This gets a non-preview release with every VS minor version. But I don't think this needs to be updated unless a new functionality from the VS SDK is needed (or if there are some transitive dependency conflicts). That's why it's fine that this is using an old version 17.2 instead of the latest 17.13 (but the real reason I chose 17.2 was because there were some conflicts with transitive dependencies IIRC).