22// System : Visual Studio Spell Checker Package
33// File : CSharpSpellCheckCodeAnalyzer.cs
44// Author : Eric Woodruff (Eric@EWoodruff.us)
5- // Updated : 12/29/2023
6- // Note : Copyright 2023, Eric Woodruff, All rights reserved
5+ // Updated : 12/14/2024
6+ // Note : Copyright 2023-2024 , Eric Woodruff, All rights reserved
77//
88// This file contains the class used to implement the C# spell check code analyzer
99//
@@ -94,6 +94,36 @@ public class CSharpSpellCheckCodeAnalyzer : DiagnosticAnalyzer
9494 IgnoreWordDescription ,
9595 "https://ewsoftware.github.io/VSSpellChecker/html/83ff9063-294f-4a18-b765-1510c86ad0d4.htm" ) ;
9696
97+ // The path to our dependencies
98+ private static readonly string codeAnalyzerPath ;
99+
100+ #endregion
101+
102+ #region Properties
103+ //=====================================================================
104+
105+ // I don't have a choice here so I'm going to use Environment and file I/O
106+ #pragma warning disable RS1035
107+ /// <summary>
108+ /// This read-only property returns the global configuration file path
109+ /// </summary>
110+ /// <value>We need this to get the analyzer code path so that we can find our dependencies because
111+ /// Visual Studio makes a copy of this assembly in some other cache location without them.</value>
112+ public static string GlobalConfigurationFilePath
113+ {
114+ get
115+ {
116+ string configPath = Path . Combine ( Environment . GetFolderPath ( Environment . SpecialFolder . LocalApplicationData ) ,
117+ @"EWSoftware\Visual Studio Spell Checker" ) ;
118+
119+ if ( ! Directory . Exists ( configPath ) )
120+ Directory . CreateDirectory ( configPath ) ;
121+
122+ return configPath ;
123+ }
124+ }
125+ #pragma warning restore RS1035
126+
97127 #endregion
98128
99129 #region Constructor
@@ -107,26 +137,65 @@ public class CSharpSpellCheckCodeAnalyzer : DiagnosticAnalyzer
107137 /// resolver to load them.</remarks>
108138 static CSharpSpellCheckCodeAnalyzer ( )
109139 {
140+ // This is a pain but for some reason starting in v17.12, Visual Studio started running the actual
141+ // code analysis in a separate copy of the assembly in a cache folder and we can no longer find our
142+ // dependencies. The odd thing is that it loads a copy to initialize it from the expected location.
143+ // As such, I'm forced to store a copy of the real path in a location the other copy can get to.
144+ // I haven't been able to find a better way around this. If you know of one, let me know.
145+ string analyzerCodePath = Path . Combine ( GlobalConfigurationFilePath , "AnalyzerCodePath.txt" ) ;
146+
147+ var analyzerPaths = new List < string > ( ) ;
148+
149+ if ( File . Exists ( analyzerCodePath ) )
150+ analyzerPaths . AddRange ( File . ReadAllLines ( analyzerCodePath ) ) ;
151+
152+ var asm = Assembly . GetExecutingAssembly ( ) ;
153+ string analyzerPath = analyzerPaths . FirstOrDefault ( p => p . StartsWith ( asm . FullName , StringComparison . Ordinal ) ) ;
154+
155+ if ( String . IsNullOrWhiteSpace ( analyzerPath ) )
156+ {
157+ string dependencyPath = Path . GetDirectoryName ( asm . Location ) ;
158+
159+ if ( File . Exists ( Path . Combine ( dependencyPath , "VisualStudio.SpellChecker.Common.dll" ) ) )
160+ {
161+ analyzerPaths . Add ( $ "{ asm . FullName } |{ dependencyPath } ") ;
162+ File . WriteAllLines ( analyzerCodePath , analyzerPaths ) ;
163+ codeAnalyzerPath = dependencyPath ;
164+ }
165+ }
166+ else
167+ {
168+ int pos = analyzerPath . IndexOf ( '|' ) ;
169+
170+ if ( pos != - 1 )
171+ codeAnalyzerPath = analyzerPath . Substring ( pos + 1 ) ;
172+ }
173+
110174 AppDomain . CurrentDomain . AssemblyResolve += ( s , e ) =>
111175 {
112176 Assembly refAsm = null ;
113177
114178 // We do see our own assembly name come through here so we'll ignore it
115- if ( ! String . IsNullOrWhiteSpace ( e . Name ) &&
116- ! e . Name . StartsWith ( Assembly . GetExecutingAssembly ( ) . GetName ( ) . Name , StringComparison . OrdinalIgnoreCase ) &&
179+ if ( ! String . IsNullOrWhiteSpace ( e . Name ) && ! String . IsNullOrWhiteSpace ( codeAnalyzerPath ) &&
180+ ! e . Name . EndsWith ( ".resources" , StringComparison . OrdinalIgnoreCase ) &&
181+ ! e . Name . StartsWith ( asm . GetName ( ) . Name , StringComparison . OrdinalIgnoreCase ) &&
117182 ! referenceAssemblies . TryGetValue ( e . Name , out refAsm ) )
118183 {
119184 int pos = e . Name . IndexOf ( ',' ) ;
120185
121186 if ( pos != - 1 )
122187 {
123- string assemblyName = Path . Combine ( Path . GetDirectoryName ( Assembly . GetExecutingAssembly ( ) . Location ) ,
124- e . Name . Substring ( 0 , pos ) + ".dll" ) ;
188+ string assemblyName = Path . Combine ( codeAnalyzerPath , e . Name . Substring ( 0 , pos ) + ".dll" ) ;
125189
126190 // If not found it's probably looking for a resources assembly that doesn't exist so
127191 // we'll ignore it.
128192 if ( File . Exists ( assemblyName ) )
193+ {
129194 refAsm = Assembly . LoadFile ( assemblyName ) ;
195+
196+ if ( refAsm . FullName != e . Name )
197+ refAsm = null ;
198+ }
130199 }
131200
132201 referenceAssemblies [ e . Name ] = refAsm ;
0 commit comments