33
44using System . Collections . Immutable ;
55using System . ComponentModel . Composition ;
6+ using System . Text . Json ;
67using Microsoft . CodeAnalysis . Workspaces . AnalyzerRedirecting ;
8+ using Microsoft . VisualStudio . Shell ;
9+ using Microsoft . VisualStudio . Shell . Interop ;
710
811// Example:
912// FullPath: "C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\8.0.8\analyzers\dotnet\System.Windows.Forms.Analyzers.dll"
1316
1417namespace Microsoft . Net . Sdk . AnalyzerRedirecting ;
1518
19+ /// <summary>
20+ /// See <c>documentation/general/analyzer-redirecting.md</c>.
21+ /// </summary>
1622[ Export ( typeof ( IAnalyzerAssemblyRedirector ) ) ]
1723public sealed class SdkAnalyzerAssemblyRedirector : IAnalyzerAssemblyRedirector
1824{
25+ private readonly IVsActivityLog ? _log ;
26+
27+ private readonly bool _enabled ;
28+
1929 private readonly string ? _insertedAnalyzersDirectory ;
2030
2131 /// <summary>
@@ -24,62 +34,91 @@ public sealed class SdkAnalyzerAssemblyRedirector : IAnalyzerAssemblyRedirector
2434 private readonly ImmutableDictionary < string , List < AnalyzerInfo > > _analyzerMap ;
2535
2636 [ ImportingConstructor ]
27- public SdkAnalyzerAssemblyRedirector ( )
28- : this ( Path . GetFullPath ( Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , @"..\..\DotNetRuntimeAnalyzers" ) ) ) { }
37+ public SdkAnalyzerAssemblyRedirector ( SVsServiceProvider serviceProvider ) : this (
38+ Path . GetFullPath ( Path . Combine ( AppDomain . CurrentDomain . BaseDirectory , @"CommonExtensions\Microsoft\DotNet" ) ) ,
39+ serviceProvider . GetService < SVsActivityLog , IVsActivityLog > ( ) )
40+ {
41+ }
2942
3043 // Internal for testing.
31- internal SdkAnalyzerAssemblyRedirector ( string ? insertedAnalyzersDirectory )
44+ internal SdkAnalyzerAssemblyRedirector ( string ? insertedAnalyzersDirectory , IVsActivityLog ? log = null )
3245 {
46+ _log = log ;
47+ var enable = Environment . GetEnvironmentVariable ( "DOTNET_ANALYZER_REDIRECTING" ) ;
48+ _enabled = ! "0" . Equals ( enable , StringComparison . OrdinalIgnoreCase ) && ! "false" . Equals ( enable , StringComparison . OrdinalIgnoreCase ) ;
3349 _insertedAnalyzersDirectory = insertedAnalyzersDirectory ;
3450 _analyzerMap = CreateAnalyzerMap ( ) ;
3551 }
3652
3753 private ImmutableDictionary < string , List < AnalyzerInfo > > CreateAnalyzerMap ( )
3854 {
55+ if ( ! _enabled )
56+ {
57+ Log ( "Analyzer redirecting is disabled." ) ;
58+ return ImmutableDictionary < string , List < AnalyzerInfo > > . Empty ;
59+ }
60+
61+ var metadataFilePath = Path . Combine ( _insertedAnalyzersDirectory , "metadata.json" ) ;
62+ if ( ! File . Exists ( metadataFilePath ) )
63+ {
64+ Log ( $ "File does not exist: { metadataFilePath } ") ;
65+ return ImmutableDictionary < string , List < AnalyzerInfo > > . Empty ;
66+ }
67+
68+ var versions = JsonSerializer . Deserialize < Dictionary < string , string > > ( File . ReadAllText ( metadataFilePath ) ) ;
69+ if ( versions is null || versions . Count == 0 )
70+ {
71+ Log ( $ "Versions are empty: { metadataFilePath } ") ;
72+ return ImmutableDictionary < string , List < AnalyzerInfo > > . Empty ;
73+ }
74+
3975 var builder = ImmutableDictionary . CreateBuilder < string , List < AnalyzerInfo > > ( StringComparer . OrdinalIgnoreCase ) ;
4076
4177 // Expects layout like:
42- // VsInstallDir\SDK\RuntimeAnalyzers\WindowsDesktopAnalyzers\8.0.8\analyzers\dotnet\System.Windows.Forms.Analyzers.dll
43- // ~~~~~~~~~~~~~~~~~~~~~~~ = topLevelDirectory
44- // ~~~~~ = versionDirectory
45- // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ = analyzerPath
78+ // VsInstallDir\DotNetRuntimeAnalyzers\WindowsDesktopAnalyzers\analyzers\dotnet\System.Windows.Forms.Analyzers.dll
79+ // ~~~~~~~~~~~~~~~~~~~~~~~ = topLevelDirectory
80+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ = analyzerPath
4681
4782 foreach ( string topLevelDirectory in Directory . EnumerateDirectories ( _insertedAnalyzersDirectory ) )
4883 {
49- foreach ( string versionDirectory in Directory . EnumerateDirectories ( topLevelDirectory ) )
84+ foreach ( string analyzerPath in Directory . EnumerateFiles ( topLevelDirectory , "*.dll" , SearchOption . AllDirectories ) )
5085 {
51- foreach ( string analyzerPath in Directory . EnumerateFiles ( versionDirectory , "*.dll" , SearchOption . AllDirectories ) )
86+ if ( ! analyzerPath . StartsWith ( topLevelDirectory , StringComparison . OrdinalIgnoreCase ) )
5287 {
53- if ( ! analyzerPath . StartsWith ( versionDirectory , StringComparison . OrdinalIgnoreCase ) )
54- {
55- continue ;
56- }
57-
58- string version = Path . GetFileName ( versionDirectory ) ;
59- string analyzerName = Path . GetFileNameWithoutExtension ( analyzerPath ) ;
60- string pathSuffix = analyzerPath . Substring ( versionDirectory . Length + 1 /* slash */ ) ;
61- pathSuffix = Path . GetDirectoryName ( pathSuffix ) ;
62-
63- AnalyzerInfo analyzer = new ( ) { FullPath = analyzerPath , ProductVersion = version , PathSuffix = pathSuffix } ;
64-
65- if ( builder . TryGetValue ( analyzerName , out var existing ) )
66- {
67- existing . Add ( analyzer ) ;
68- }
69- else
70- {
71- builder . Add ( analyzerName , [ analyzer ] ) ;
72- }
88+ continue ;
89+ }
90+
91+ string subsetName = Path . GetFileName ( topLevelDirectory ) ;
92+ if ( ! versions . TryGetValue ( subsetName , out string version ) )
93+ {
94+ continue ;
95+ }
96+
97+ string analyzerName = Path . GetFileNameWithoutExtension ( analyzerPath ) ;
98+ string pathSuffix = analyzerPath . Substring ( topLevelDirectory . Length + 1 /* slash */ ) ;
99+ pathSuffix = Path . GetDirectoryName ( pathSuffix ) ;
100+
101+ AnalyzerInfo analyzer = new ( ) { FullPath = analyzerPath , ProductVersion = version , PathSuffix = pathSuffix } ;
102+
103+ if ( builder . TryGetValue ( analyzerName , out var existing ) )
104+ {
105+ existing . Add ( analyzer ) ;
106+ }
107+ else
108+ {
109+ builder . Add ( analyzerName , [ analyzer ] ) ;
73110 }
74111 }
75112 }
76113
114+ Log ( $ "Loaded analyzer map ({ builder . Count } ): { _insertedAnalyzersDirectory } ") ;
115+
77116 return builder . ToImmutable ( ) ;
78117 }
79118
80119 public string ? RedirectPath ( string fullPath )
81120 {
82- if ( _analyzerMap . TryGetValue ( Path . GetFileNameWithoutExtension ( fullPath ) , out var analyzers ) )
121+ if ( _enabled && _analyzerMap . TryGetValue ( Path . GetFileNameWithoutExtension ( fullPath ) , out var analyzers ) )
83122 {
84123 foreach ( AnalyzerInfo analyzer in analyzers )
85124 {
@@ -134,4 +173,12 @@ static bool areVersionMajorMinorPartEqual(string version1, string version2)
134173 return 0 == string . Compare ( version1 , 0 , version2 , 0 , secondDotIndex , StringComparison . OrdinalIgnoreCase ) ;
135174 }
136175 }
176+
177+ private void Log ( string message )
178+ {
179+ _log ? . LogEntry (
180+ ( uint ) __ACTIVITYLOG_ENTRYTYPE . ALE_INFORMATION ,
181+ nameof ( SdkAnalyzerAssemblyRedirector ) ,
182+ message ) ;
183+ }
137184}
0 commit comments